Compare commits

..

7 Commits

Author SHA1 Message Date
OpenDev Sysadmins
582b52b1b8 OpenDev Migration Patch
This commit was bulk generated and pushed by the OpenDev sysadmins
as a part of the Git hosting and code review systems migration
detailed in these mailing list posts:

http://lists.openstack.org/pipermail/openstack-discuss/2019-March/003603.html
http://lists.openstack.org/pipermail/openstack-discuss/2019-April/004920.html

Attempts have been made to correct repository namespaces and
hostnames based on simple pattern matching, but it's possible some
were updated incorrectly or missed entirely. Please reach out to us
via the contact information listed at https://opendev.org/ with any
questions you may have.
2019-04-19 19:45:01 +00:00
Ian Wienand
4d0eb4c55a Replace openstack.org git:// URLs with https://
This is a mechanically generated change to replace openstack.org
git:// URLs with https:// equivalents.

This is in aid of a planned future move of the git hosting
infrastructure to a self-hosted instance of gitea (https://gitea.io),
which does not support the git wire protocol at this stage.

This update should result in no functional change.

For more information see the thread at

 http://lists.openstack.org/pipermail/openstack-discuss/2019-March/003825.html

Change-Id: I289933e0a3c5703f4d972a3c76eccde1bdddce0b
2019-03-25 07:29:14 +00:00
Zuul
3e9b0d678e Merge "import zuul job settings from project-config" into stable/rocky 2018-08-28 07:52:59 +00:00
Alexander Chadin
b34b746d1c fix watcher actionplan show command
This patch set is intended for Python 3.x support and replace
itervalues() method with values().

Change-Id: I6852239d2d6634a4304c87a1694e64479f560de1
Closes-Bug: #1786784
(cherry picked from commit c1fd569b76)
2018-08-20 13:52:59 +00:00
Nguyen Hai
baf0133c5b import zuul job settings from project-config
This is a mechanically generated patch to complete step 1 of moving
the zuul job settings out of project-config and into each project
repository.

Because there will be a separate patch on each branch, the branch
specifiers for branch-specific jobs have been removed.

Because this patch is generated by a script, there may be some
cosmetic changes to the layout of the YAML file(s) as the contents are
normalized.

See the python3-first goal document for details:
https://governance.openstack.org/tc/goals/stein/python3-first.html

Change-Id: I217c5b98e4398d105c1f352db25fb9f084262582
Story: #2002586
Task: #24344
2018-08-19 00:59:19 +09:00
e5bd6e00cb Update UPPER_CONSTRAINTS_FILE for stable/rocky
The new stable upper-constraints file is only available
after the openstack/requirements repository is branched.
This will happen around the RC1 timeframe.

Recheck and merge this change once the requirements
repository has been branched.

The CI system will work with this patch before the requirements
repository is branched because zuul configues the job to run
with a local copy of the file and defaults to the master branch.
However, accepting the patch will break the test configuration
on developers' local systems, so please wait until after the
requirements repository is branched to merge the patch.

Change-Id: Iaef151a13d99a2eb975283a2f175245547c455e1
2018-08-08 09:36:22 +00:00
827d474cc4 Update .gitreview for stable/rocky
Change-Id: I4d2d7491072d4470300d175ecd9c9143e4b798f5
2018-08-08 09:36:21 +00:00
91 changed files with 1174 additions and 2303 deletions

View File

@@ -1,11 +0,0 @@
[run]
branch = True
source = watcherclient
omit =
watcherclient/tests/*
[report]
ignore_errors = True
exclude_lines =
@abc.abstract
raise NotImplementedError

9
.gitignore vendored
View File

@@ -23,12 +23,11 @@ lib64
pip-log.txt
# Unit test / coverage reports
.coverage*
.coverage
.tox
nosetests.xml
.stestr/
.testrepository
.venv
.testrepository/
# Translations
*.mo
@@ -57,10 +56,6 @@ ChangeLog
sftp-config.json
/.idea/
/cover/
# Desktop Service Store
*.DS_Store
# Atom
.remote-sync.json

View File

@@ -2,3 +2,4 @@
host=review.opendev.org
port=29418
project=openstack/python-watcherclient.git
defaultbranch=stable/rocky

View File

@@ -1,3 +1,3 @@
[DEFAULT]
test_path=./watcherclient/tests/unit
top_dir=./
test_path=${OS_TEST_PATH:-./watcherclient/tests/functional}
top_dir=./

7
.testr.conf Normal file
View File

@@ -0,0 +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:-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

View File

@@ -1,37 +1,18 @@
- job:
name: python-watcherclient-functional
parent: devstack-tox-functional
timeout: 7200
required-projects:
- openstack/watcher
- openstack/python-watcherclient
vars:
# Run cross-project watcherclient functional tests on watcher repo.
zuul_work_dir: src/opendev.org/openstack/python-watcherclient
openrc_enable_export: true
devstack_plugins:
watcher: https://opendev.org/openstack/watcher
devstack_services:
watcher-api: true
watcher-decision-engine: true
watcher-applier: true
s-account: false
s-container: false
s-object: false
s-proxy: false
irrelevant-files:
- ^.*\.rst$
- ^doc/.*$
- ^releasenotes/.*$
- project:
templates:
- openstack-cover-jobs
- openstack-python3-jobs
- publish-openstack-docs-pti
- openstack-python-jobs
- openstack-python35-jobs
- publish-openstack-sphinx-docs
- check-requirements
- openstackclient-plugin-jobs
check:
jobs:
- python-watcherclient-functional
- watcherclient-tempest-functional:
voting: false
- openstack-tox-lower-constraints
- openstack-tox-cover:
voting: false
gate:
jobs:
- openstack-tox-lower-constraints

View File

@@ -24,7 +24,7 @@ migration, increased energy efficiency and more!
* Free software: Apache license
* Wiki: https://wiki.openstack.org/wiki/Watcher
* Source: https://opendev.org/openstack/python-watcherclient
* Source: https://git.openstack.org/cgit/openstack/python-watcher
* Bugs: https://bugs.launchpad.net/watcher
Installation

2
babel.cfg Normal file
View File

@@ -0,0 +1,2 @@
[python: **.py]

View File

@@ -1,8 +0,0 @@
# The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
openstackdocstheme>=2.2.1 # Apache-2.0
sphinx>=2.0.0,!=2.1.0 # BSD
sphinxcontrib-apidoc>=0.2.0 # BSD

View File

@@ -24,7 +24,7 @@ The watcher client is the command-line interface (CLI) for the
Infrastructure Optimization service (watcher) API
and its extensions.
This chapter documents watcherclient version ``4.9.0``.
This chapter documents :command:`watcher` version ``1.3.0``.
For help on a specific :command:`watcher` command, enter:
@@ -136,7 +136,7 @@ watcher optional arguments
Defaults to ``env[OS_AUTH_TOKEN]``.
``--os-watcher-api-version <os-watcher-api-version>``
Defaults to ``env[OS_INFRA_OPTIM_API_VERSION]``.
Defaults to ``env[OS_WATCHER_API_VERSION]``.
``--os-endpoint-type OS_ENDPOINT_TYPE``
Defaults to ``env[OS_ENDPOINT_TYPE]`` or "publicURL"
@@ -157,7 +157,7 @@ watcher action list
[--quote {all,minimal,none,nonnumeric}]
[--action-plan <action-plan>] [--audit <audit>]
[--detail] [--limit <limit>] [--sort-key <field>]
[--sort-dir <direction>] [--marker <marker>]
[--sort-dir <direction>]
List information on retrieved actions.
@@ -186,10 +186,6 @@ List information on retrieved actions.
``--sort-dir <direction>``
Sort direction: "asc" (the default) or "desc".
``--marker <marker>``
UUID of the last action in the previous page; displays
list of actions after "marker".
.. _watcher_action_show:
watcher action show
@@ -214,37 +210,6 @@ Show detailed information about a given action.
``-h, --help``
show this help message and exit
.. _watcher_action_update:
watcher action update
---------------------
.. code-block:: console
usage: watcher action update [-h] [-f {html,json,shell,table,value,yaml}]
[-c COLUMN] [--max-width <integer>] [--fit-width]
[--print-empty] [--noindent] [--prefix PREFIX]
[--state <state>] [--reason <reason>] <action>
Update action command.
**Positional arguments:**
``<action>``
UUID of the action
**Optional arguments:**
``-h, --help``
show this help message and exit
``--state <state>``
New state for the action (e.g., SKIPPED)
``--reason <reason>``
Reason for the action state change.
.. _watcher_actionplan_cancel:
watcher actionplan cancel
@@ -330,8 +295,7 @@ watcher actionplan list
[--fit-width] [--print-empty] [--noindent]
[--quote {all,minimal,none,nonnumeric}]
[--audit <audit>] [--detail] [--limit <limit>]
[--marker <actionplan>] [--sort-key <field>]
[--sort-dir <direction>]
[--sort-key <field>] [--sort-dir <direction>]
List information on retrieved action plans.
@@ -351,10 +315,6 @@ List information on retrieved action plans.
0 for no limit. Default is the maximum number used by
the Watcher API Service.
``--marker <actionplan>``
The last actionplan UUID of the previous page;
displays list of actionplans after "marker".
``--sort-key <field>``
Action Plan field that will be used for sorting.
@@ -506,7 +466,7 @@ Delete audit command.
**Positional arguments:**
``<audit>``
UUID or name of the audit
UUID of the audit
**Optional arguments:**
@@ -572,7 +532,7 @@ Show detailed information about a given audit.
**Positional arguments:**
``<audit>``
UUID or name of the audit
UUID of the audit
**Optional arguments:**
@@ -596,7 +556,7 @@ Update audit command.
**Positional arguments:**
``<audit>``
UUID or name of the audit.
UUID of the audit.
``<op>``
Operation: 'add', 'replace', or 'remove'.
@@ -651,43 +611,38 @@ Create new audit template.
``--scope <path>``
Part of the cluster on which an audit will be done.
Can be provided either in yaml or json file.
YAML example:
::
---
- host_aggregates:
- id: 1
- id: 2
- id: 3
YAML example:
---- host_aggregates:
- id: 1
- id: 2
- id: 3
- availability_zones:
- name: AZ1
- name: AZ2
- name: AZ1
- name: AZ2
- exclude:
- instances:
- uuid: UUID1
- uuid: UUID2
- compute_nodes:
- name: compute1
JSON example:
::
[
{'host_aggregates': [
{'id': 1},
{'id': 2},
{'id': 3}]},
{'availability_zones': [
{'name': 'AZ1'},
{'name': 'AZ2'}]},
{'exclude': [
{'instances': [
{'uuid': 'UUID1'},
{'uuid': 'UUID2'}]},
{'compute_nodes': [
{'name': 'compute1'}]}]}
]
- instances:
- uuid: UUID1
- uuid: UUID2
- compute_nodes:
- name: compute1
JSON example:
[{'host_aggregates': [
{'id': 1},
{'id': 2},
{'id': 3}]},
{'availability_zones': [
{'name': 'AZ1'},
{'name': 'AZ2'}]},
{'exclude': [
{'instances': [
{'uuid': 'UUID1'},
{'uuid': 'UUID2'}
]},
{'compute_nodes': [
{'name': 'compute1'}
]}
]}]
.. _watcher_audittemplate_delete:
@@ -725,7 +680,7 @@ watcher audittemplate list
[--detail] [--goal <goal>]
[--strategy <strategy>] [--limit <limit>]
[--sort-key <field>]
[--sort-dir <direction>][--marker <marker>]
[--sort-dir <direction>]
List information on retrieved audit templates.
@@ -754,10 +709,6 @@ List information on retrieved audit templates.
``--sort-dir <direction>``
Sort direction: "asc" (the default) or "desc".
``--marker <marker>``
UUID of the last audittemplate in the previous page; displays
list of audittemplates after "marker".
.. _watcher_audittemplate_show:
watcher audittemplate show
@@ -830,7 +781,7 @@ watcher goal list
[--print-empty] [--noindent]
[--quote {all,minimal,none,nonnumeric}] [--detail]
[--limit <limit>] [--sort-key <field>]
[--sort-dir <direction>][--marker <marker>]
[--sort-dir <direction>]
List information on retrieved goals.
@@ -853,10 +804,6 @@ List information on retrieved goals.
``--sort-dir <direction>``
Sort direction: "asc" (the default) or "desc".
``--marker <marker>``
UUID of the last goal in the previous page; displays
list of goals after "marker".
.. _watcher_goal_show:
watcher goal show
@@ -894,7 +841,7 @@ watcher scoringengine list
[--quote {all,minimal,none,nonnumeric}]
[--detail] [--limit <limit>]
[--sort-key <field>]
[--sort-dir <direction>][--marker <marker>]
[--sort-dir <direction>]
List information on retrieved scoring engines.
@@ -917,10 +864,6 @@ List information on retrieved scoring engines.
``--sort-dir <direction>``
Sort direction: "asc" (the default) or "desc".
``--marker <marker>``
UUID of the last scoringengine in the previous page; displays
list of scoringengines after "marker".
.. _watcher_scoringengine_show:
watcher scoringengine show
@@ -1005,40 +948,6 @@ Show detailed information about a given service.
``-h, --help``
show this help message and exit
.. _watcher_datamodel_list:
watcher datamodel list
----------------------
.. code-block:: console
usage: watcher datamodel list [-h] [-f {csv,json,table,value,yaml}]
[-c COLUMN] [--max-width <integer>]
[--fit-width] [--print-empty] [--noindent]
[--quote {all,minimal,none,nonnumeric}]
[--sort-column SORT_COLUMN] [--type <type>]
[--audit <audit>] [--detail]
List information on retrieved data model.
**Optional arguments:**
``-h, --help``
show this help message and exit
``--type <type>``
Type of Datamodel user want to list. Supported values:
compute. Future support values: storage, baremetal.
Default type is compute.
``--audit <audit>``
UUID of the audit. used to filter data model
by the scope in audit.
``--detail``
Show detailed information about data model.
.. _watcher_strategy_list:
watcher strategy list
@@ -1052,7 +961,6 @@ watcher strategy list
[--quote {all,minimal,none,nonnumeric}]
[--goal <goal>] [--detail] [--limit <limit>]
[--sort-key <field>] [--sort-dir <direction>]
[--marker <marker>]
List information on retrieved strategies.
@@ -1078,10 +986,6 @@ List information on retrieved strategies.
``--sort-dir <direction>``
Sort direction: "asc" (the default) or "desc".
``--marker <marker>``
UUID of the last strategy in the previous page; displays
list of strategies after "marker".
.. _watcher_strategy_show:
watcher strategy show
@@ -1106,28 +1010,3 @@ Show detailed information about a given strategy.
``-h, --help``
show this help message and exit
.. _watcher_strategy_state:
watcher strategy state
----------------------
.. code-block:: console
usage: watcher strategy state [-h] [-f {csv,html,json,table,value,yaml}]
[-c COLUMN] [--max-width <integer>]
[--fit-width] [--print-empty] [--noindent]
[--quote {all,minimal,none,nonnumeric}]
[--sort-column SORT_COLUMN]
<strategy>
Retrieve information about strategy requirements.
**Positional arguments:**
``<strategy>``
Name of the strategy
**Optional arguments:**
``-h, --help``
show this help message and exit

View File

@@ -11,7 +11,7 @@ variables::
export OS_USERNAME=user
export OS_PASSWORD=pass
export OS_TENANT_ID=b363706f891f48019483f8bd6503c54b
export OS_AUTH_URL=http://auth.example.com:5000/v3/
export OS_AUTH_URL=http://auth.example.com:5000/v2.0
The command line tool will attempt to reauthenticate using your
provided credentials for every request. You can override this behavior

View File

@@ -33,7 +33,7 @@ environment variables::
$ 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/v3/
$ 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

View File

@@ -32,7 +32,7 @@ environment variables::
$ 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/v3/
$ 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
@@ -52,7 +52,7 @@ environment variable. (It defaults to the first in the list returned.)
Watcher CLI supports bash completion. The command-line tool can automatically
fill partially typed commands. To use this feature, source the below file
(available at
https://opendev.org/openstack/python-watcherclient/src/branch/master/tools/watcher.bash_completion)
https://git.openstack.org/cgit/openstack/python-watcherclient/tree/tools/watcher.bash_completion)
to your terminal and then bash completion should work::
$ . watcher.bash_completion

View File

@@ -12,26 +12,20 @@
# limitations under the License.
from watcherclient import version as watcherclient_version
# -- General configuration ----------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc',
'sphinx.ext.viewcode',
'sphinxcontrib.apidoc',
'openstackdocstheme',
]
# autodoc generation is a bit aggressive and a nuisance when doing heavy
# text edit cycles.
# execute "export SPHINX_DEBUG=1" in your terminal to disable
# sphinxcontrib.apidoc options
apidoc_module_dir = '../../watcherclient'
apidoc_output_dir = 'reference/api'
apidoc_excluded_paths = [
'tests/*']
apidoc_separate_modules = True
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
@@ -42,8 +36,18 @@ source_suffix = '.rst'
master_doc = 'index'
# General information about the project.
project = 'python-watcherclient'
copyright = 'OpenStack Foundation'
project = u'python-watcherclient'
copyright = u'OpenStack Foundation'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
# The full version, including alpha/beta/rc tags.
release = watcherclient_version.version_info.release_string()
# The short X.Y version.
version = watcherclient_version.version_info.version_string()
# A list of ignored prefixes for module index sorting.
modindex_common_prefix = ['watcherclient.']
@@ -56,7 +60,7 @@ add_function_parentheses = True
add_module_names = True
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'native'
pygments_style = 'sphinx'
# -- Options for HTML output --------------------------------------------------
@@ -79,27 +83,17 @@ latex_documents = [
(
'index',
'%s.tex' % project,
'%s Documentation' % project,
'OpenStack Foundation', 'manual'
u'%s Documentation' % project,
u'OpenStack Foundation', 'manual'
),
]
# Disable usage of xindy https://bugzilla.redhat.com/show_bug.cgi?id=1643664
latex_use_xindy = False
latex_domain_indices = False
latex_elements = {
'makeindex': '',
'printindex': '',
'preamble': r'\setcounter{tocdepth}{3}',
}
# openstackdocstheme options
openstackdocs_repo_name = 'openstack/python-watcherclient'
openstackdocs_pdf_link = True
openstackdocs_bug_project = 'python-watcherclient'
openstackdocs_bug_tag = ''
repository_name = 'openstack/python-watcherclient'
bug_project = 'python-watcherclient'
bug_tag = ''
# Must set this variable to include year, month, day, hours, and minutes.
html_last_updated_fmt = '%Y-%m-%d %H:%M'
#html_theme_options = {"show_other_versions": "True"}

View File

@@ -8,24 +8,19 @@ If you're interested in contributing to the python-watcherclient project,
the following will help get you started.
Developer Certificate of Origin
-------------------------------
Contributor License Agreement
-----------------------------
.. index::
single: license; agreement
In order to contribute to the python-watcherclient project, you need to adhere
to the `Developer Certificate of Origin`_. OpenStack utilizes the Developer
Certificate of Origin (DCO) as a lightweight means to confirm that you are
entitled to contribute the code you submit. This ensures that you are
providing your contributions under the project's license and that you have
the right to do so.
.. _Developer Certificate of Origin: https://developercertificate.org/
In order to contribute to the python-watcherclient project, you need to have
signed OpenStack's contributor's agreement.
.. seealso::
* https://docs.openstack.org/contributors/common/dco.html
* https://docs.openstack.org/infra/manual/developers.html
* https://wiki.openstack.org/CLA
LaunchPad Project
-----------------
@@ -49,11 +44,11 @@ Bug tracker
https://launchpad.net/python-watcherclient
Mailing list (prefix subjects with ``[watcher]`` for faster responses)
http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-discuss
http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-dev
Code Hosting
https://opendev.org/openstack/python-watcherclient
https://git.openstack.org/cgit/openstack/python-watcherclient
Code Review
https://review.opendev.org/#/q/status:open+project:openstack/python-watcherclient,n,z
https://review.openstack.org/#/q/status:open+project:openstack/python-watcherclient,n,z

View File

@@ -5,6 +5,6 @@ Installation
If you have `virtualenvwrapper <https://virtualenvwrapper.readthedocs.org/en/latest/install.html>`_ installed::
$ mkvirtualenv python-watcherclient
$ git clone https://opendev.org/openstack/python-watcherclient
$ git clone https://git.openstack.org/openstack/python-watcherclient
$ cd python-watcherclient && python setup.py install
$ pip install -r ./requirements.txt

View File

@@ -0,0 +1,8 @@
======================
Python API Reference
======================
.. toctree::
:maxdepth: 2
autoindex

View File

@@ -10,5 +10,5 @@ done so, you can use the API like so.
.. toctree::
:maxdepth: 2
api/modules
api/index
api_v1

91
lower-constraints.txt Normal file
View File

@@ -0,0 +1,91 @@
alabaster==0.7.10
appdirs==1.4.3
asn1crypto==0.23.0
Babel==2.5.3
certifi==2018.1.18
cffi==1.7.0
chardet==3.0.4
cliff==2.11.0
cmd2==0.8.2
coverage==4.0
cryptography==2.1
debtcollector==1.19.0
decorator==4.2.1
deprecation==2.0
docutils==0.11
dogpile.cache==0.6.5
dulwich==0.15.0
extras==1.0.0
fasteners==0.7.0
fixtures==3.0.0
flake8==2.5.5
future==0.16.0
hacking==0.12.0
idna==2.6
imagesize==0.7.1
iso8601==0.1.12
Jinja2==2.10
jmespath==0.9.3
jsonpatch==1.21
jsonpointer==2.0
jsonschema==2.6.0
keystoneauth1==3.4.0
linecache2==1.0.0
MarkupSafe==1.0
mccabe==0.2.1
mock==2.0.0
monotonic==1.4
mox3==0.20.0
msgpack-python==0.4.0
munch==2.2.0
netaddr==0.7.19
netifaces==0.10.6
openstackdocstheme==1.18.1
openstacksdk==0.12.0
os-client-config==1.29.0
os-service-types==1.2.0
os-testr==1.0.0
osc-lib==1.10.0
oslo.concurrency==3.25.0
oslo.config==5.2.0
oslo.context==2.19.2
oslo.i18n==3.20.0
oslo.log==3.36.0
oslo.serialization==2.18.0
oslo.utils==3.36.0
oslotest==3.2.0
packaging==17.1
paramiko==2.0.0
pbr==3.1.1
pep8==1.5.7
prettytable==0.7.2
pyasn1==0.1.8
pycparser==2.18
pyflakes==0.8.1
Pygments==2.2.0
pyinotify==0.9.6
pyparsing==2.2.0
pyperclip==1.6.0
python-dateutil==2.5.3
python-mimeparse==1.6.0
python-subunit==1.0.0
pytz==2018.3
PyYAML==3.12
requests==2.18.4
requestsexceptions==1.4.0
rfc3986==0.3.1
simplejson==3.13.2
six==1.11.0
snowballstemmer==1.2.1
Sphinx==1.6.5
sphinxcontrib-websupport==1.0.1
stestr==1.0.0
stevedore==1.28.0
tempest==17.1.0
testrepository==0.0.18
testscenarios==0.4
testtools==2.2.0
traceback2==1.4.0
unittest2==1.1.0
urllib3==1.22
wrapt==1.10.11

View File

@@ -1,3 +0,0 @@
[build-system]
requires = ["pbr>=6.0.0", "setuptools>=64.0.0"]
build-backend = "pbr.build"

View File

@@ -1,24 +0,0 @@
---
features:
- |
Added support for updating action state through the new
``openstack optimize action update`` command. This feature allows
operators to manually change action states. The command
supports the following options:
* ``--state <state>`` - New state for the action (required)
* ``--reason <reason>`` - Optional reason for the state change
Currently, the only use case for this update is to Skip an action
before starting an Action Plan with an optional reason by setting
the state to SKIPPED:
$ openstack optimize action update --state SKIPPED --reason "Manual skip" <action-uuid>
This feature requires Watcher API microversion 1.5 or higher.
upgrade:
- |
The maximum supported API version has been increased from 1.1 to 1.5
to support the new action update functionality. This change maintains
full backward compatibility with existing deployments.

View File

@@ -1,6 +0,0 @@
---
upgrade:
- |
Python 2.7 support has been dropped. Last release of python-watcherclient
to support py2.7 is OpenStack Train. The minimum version of Python now
supported by python-watcherclient is Python 3.6.

View File

@@ -1,6 +0,0 @@
---
upgrade:
- |
watcher client now requires python 3.10 or newer.
The last release to support ``3.9`` was ``2025.1``.
Please ensure you have a supported python version before upgrading.

View File

@@ -1,8 +1,14 @@
cliff!=2.9.0,>=2.11.0 # Apache-2.0
osc-lib>=1.10.0 # Apache-2.0
oslo.i18n>=3.20.0 # Apache-2.0
oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0
oslo.utils>=3.36.0 # Apache-2.0
pbr!=2.1.0,>=3.1.1 # Apache-2.0
# The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
Babel!=2.4.0,>=2.3.4 # BSD
cliff!=2.9.0,>=2.8.0 # Apache-2.0
osc-lib>=1.8.0 # Apache-2.0
oslo.i18n>=3.15.3 # Apache-2.0
oslo.utils>=3.33.0 # Apache-2.0
pbr!=2.1.0,>=2.0.0 # Apache-2.0
PrettyTable<0.8,>=0.7.1 # BSD
keystoneauth1>=3.4.0 # Apache-2.0
PyYAML>=3.13 # MIT
six>=1.10.0 # MIT
PyYAML>=3.12 # MIT

View File

@@ -1,12 +1,11 @@
[metadata]
name = python-watcherclient
summary = Python client library for Watcher API
description_file =
description-file =
README.rst
author = OpenStack
author_email = openstack-discuss@lists.openstack.org
home_page = https://docs.openstack.org/python-watcherclient/latest/
python_requires = >=3.10
author-email = openstack-dev@lists.openstack.org
home-page = https://docs.openstack.org/python-watcherclient/latest/
classifier =
Environment :: OpenStack
Intended Audience :: Information Technology
@@ -14,12 +13,10 @@ classifier =
License :: OSI Approved :: Apache Software License
Operating System :: POSIX :: Linux
Programming Language :: Python
Programming Language :: Python :: 2
Programming Language :: Python :: 2.7
Programming Language :: Python :: 3
Programming Language :: Python :: 3.10
Programming Language :: Python :: 3.11
Programming Language :: Python :: 3.12
Programming Language :: Python :: 3.13
Programming Language :: Python :: 3 :: Only
Programming Language :: Python :: 3.5
[files]
packages =
@@ -28,6 +25,8 @@ packages =
[entry_points]
console_scripts =
watcher = watcherclient.shell:main
tempest.test_plugins =
watcherclient_tests = watcherclient.plugin:WatcherClientTempestPlugin
openstack.cli.extension =
infra_optim = watcherclient.osc.plugin
@@ -54,7 +53,6 @@ openstack.infra_optim.v1 =
optimize_audit_delete = watcherclient.v1.audit_shell:DeleteAudit
optimize_actionplan_show = watcherclient.v1.action_plan_shell:ShowActionPlan
optimize_actionplan_delete = watcherclient.v1.action_plan_shell:DeleteActionPlan
optimize_actionplan_list = watcherclient.v1.action_plan_shell:ListActionPlan
optimize_actionplan_update = watcherclient.v1.action_plan_shell:UpdateActionPlan
optimize_actionplan_start = watcherclient.v1.action_plan_shell:StartActionPlan
@@ -62,7 +60,6 @@ openstack.infra_optim.v1 =
optimize_action_show = watcherclient.v1.action_shell:ShowAction
optimize_action_list = watcherclient.v1.action_shell:ListAction
optimize_action_update = watcherclient.v1.action_shell:UpdateAction
optimize_scoringengine_show = watcherclient.v1.scoring_engine_shell:ShowScoringEngine
optimize_scoringengine_list = watcherclient.v1.scoring_engine_shell:ListScoringEngine
@@ -70,8 +67,6 @@ openstack.infra_optim.v1 =
optimize_service_show = watcherclient.v1.service_shell:ShowService
optimize_service_list = watcherclient.v1.service_shell:ListService
optimize_datamodel_list = watcherclient.v1.data_model_shell:ListDataModel
# The same as above but used by the 'watcher' command
watcherclient.v1 =
goal_show = watcherclient.v1.goal_shell:ShowGoal
@@ -102,7 +97,6 @@ watcherclient.v1 =
action_show = watcherclient.v1.action_shell:ShowAction
action_list = watcherclient.v1.action_shell:ListAction
action_update = watcherclient.v1.action_shell:UpdateAction
scoringengine_show = watcherclient.v1.scoring_engine_shell:ShowScoringEngine
scoringengine_list = watcherclient.v1.scoring_engine_shell:ListScoringEngine
@@ -110,4 +104,31 @@ watcherclient.v1 =
service_show = watcherclient.v1.service_shell:ShowService
service_list = watcherclient.v1.service_shell:ListService
datamodel_list = watcherclient.v1.data_model_shell:ListDataModel
[pbr]
autodoc_index_modules = True
autodoc_exclude_modules =
watcherclient.tests.*
api_doc_dir = reference/api
[build_sphinx]
source-dir = doc/source
build-dir = doc/build
all_files = 1
warning-is-error = 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

View File

@@ -13,8 +13,17 @@
# See the License for the specific language governing permissions and
# limitations under the License.
# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
import setuptools
# In python < 2.7.4, a lazy loading of package `pbr` will break
# setuptools if some other modules registered functions in `atexit`.
# solution from: http://bugs.python.org/issue15881#msg170215
try:
import multiprocessing # noqa
except ImportError:
pass
setuptools.setup(
setup_requires=['pbr>=2.0.0'],
pbr=True)

View File

@@ -1,8 +1,19 @@
# The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
coverage!=4.4,>=4.0 # Apache-2.0
fixtures>=3.0.0 # Apache-2.0/BSD
hacking>=7.0.0,<7.1.0 # Apache-2.0
hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0
mock>=2.0.0 # BSD
openstackdocstheme>=1.18.1 # Apache-2.0
oslotest>=3.2.0 # Apache-2.0
stestr>=2.0.0 # Apache-2.0
python-subunit>=1.0.0 # Apache-2.0/BSD
sphinx!=1.6.6,!=1.6.7,>=1.6.2 # BSD
testrepository>=0.0.18 # Apache-2.0/BSD
testscenarios>=0.4 # Apache-2.0/BSD
testtools>=2.2.0 # MIT
tempest>=17.1.0 # Apache-2.0
# Needed for pypi packaging
wheel>=0.24.0 # MIT

79
tox.ini
View File

@@ -1,22 +1,19 @@
[tox]
minversion = 3.18.0
envlist = py3,pep8
minversion = 1.8
envlist = py35,py27,pep8
skipsdist = True
[testenv]
usedevelop = True
passenv = ZUUL_CACHE_DIR
REQUIREMENTS_PIP_LOCATION
install_command = pip install {opts} {packages}
deps =
-c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}
-r{toxinidir}/test-requirements.txt
-r{toxinidir}/requirements.txt
allowlist_externals =
rm
install_command =
constraints: pip install -U --force-reinstall -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt?h=stable/rocky} {opts} {packages}
pip install -U {opts} {packages}
setenv =
VIRTUAL_ENV={envdir}
OS_TEST_PATH=./watcherclient/tests/unit
deps = -r{toxinidir}/test-requirements.txt
commands = rm -f .testrepository/times.dbm
# The --test-path is defined in .stestr.conf
stestr run {posargs}
stestr slowest
python setup.py testr --slowest --testr-args='{posargs}'
[testenv:pep8]
basepython = python3
@@ -28,43 +25,38 @@ commands = {posargs}
[testenv:cover]
basepython = python3
setenv =
PYTHON=coverage run --source watcherclient --parallel-mode
commands =
stestr run {posargs}
coverage combine
coverage html -d cover
coverage xml -o cover/coverage.xml
coverage report
python setup.py testr --coverage --testr-args='{posargs}'
coverage report
[testenv:docs]
basepython = python3
deps =
-c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}
-r{toxinidir}/doc/requirements.txt
commands = sphinx-build -W -b html doc/source doc/build/html
[testenv:pdf-docs]
basepython = python3
deps = {[testenv:docs]deps}
allowlist_externals =
rm
make
commands =
rm -rf doc/build/pdf
sphinx-build -W -b latex doc/source doc/build/pdf
make -C doc/build/pdf
commands = python setup.py build_sphinx
[testenv:debug]
basepython = python3
commands = oslo_debug_helper -t watcherclient/tests/unit {posargs}
[testenv:functional]
basepython = python2.7
passenv =
OS_PROJECT_DOMAIN_NAME
OS_USER_DOMAIN_NAME
OS_PROJECT_NAME
OS_USERNAME
OS_PASSWORD
OS_AUTH_URL
OS_IDENTITY_API_VERSION
OS_IMAGE_API_VERSION
setenv =
OS_TEST_PATH = ./watcherclient/tests/functional
commands = python setup.py testr --slowest --testr-args='--concurrency=1 {posargs}'
[flake8]
# E123, E125 skipped as they are invalid PEP-8.
show-source = True
enable-extensions = H203,H106
ignore = E123,E125,W504
ignore = E123,E125
builtins = _
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build
@@ -75,7 +67,10 @@ commands = python setup.py bdist_wheel
[hacking]
import_exceptions = watcherclient._i18n
[testenv:functional]
passenv = OS_*
commands =
stestr --test-path=./watcherclient/tests/client_functional/ run --concurrency=1 {posargs}
[testenv:lower-constraints]
basepython = python3
install_command = pip install -U {opts} {packages}
deps =
-c{toxinidir}/lower-constraints.txt
-r{toxinidir}/test-requirements.txt
-r{toxinidir}/requirements.txt

View File

@@ -17,7 +17,6 @@
import pbr.version
from watcherclient import client
from watcherclient.common import api_versioning
from watcherclient import exceptions
@@ -25,11 +24,3 @@ __version__ = pbr.version.VersionInfo(
'python-watcherclient').version_string()
__all__ = ['client', 'exceptions', ]
API_MIN_VERSION = api_versioning.APIVersion("1.0")
# The max version should be the latest version that is supported in the client,
# not necessarily the latest that the server can provide. This is only bumped
# when client supported the max version, and bumped sequentially, otherwise
# the client may break due to server side new version may include some
# backward incompatible change.
API_MAX_VERSION = api_versioning.APIVersion("1.5")

View File

@@ -28,7 +28,7 @@ def get_client(api_version, os_auth_token=None, watcher_url=None,
os_service_type=None, os_endpoint_type=None,
insecure=None, timeout=None, os_cacert=None, ca_file=None,
os_cert=None, cert_file=None, os_key=None, key_file=None,
os_infra_optim_api_version=None, max_retries=None,
os_watcher_api_version=None, max_retries=None,
retry_interval=None, session=None, os_endpoint_override=None,
**ignored_kwargs):
"""Get an authenticated client, based on the credentials.
@@ -61,7 +61,7 @@ def get_client(api_version, os_auth_token=None, watcher_url=None,
:param cert_file: path to cert file, deprecated in favour of os_cert
:param os_key: path to key file
:param key_file: path to key file, deprecated in favour of os_key
:param os_infra_optim_api_version: watcher API version to use
:param os_watcher_api_version: watcher API version to use
:param max_retries: Maximum number of retries in case of conflict error
:param retry_interval: Amount of time (in seconds) between retries in case
of conflict error
@@ -75,7 +75,7 @@ def get_client(api_version, os_auth_token=None, watcher_url=None,
project_id = (os_project_id or os_tenant_id)
project_name = (os_project_name or os_tenant_name)
kwargs = {
'os_infra_optim_api_version': os_infra_optim_api_version,
'os_watcher_api_version': os_watcher_api_version,
'max_retries': max_retries,
'retry_interval': retry_interval,
}
@@ -182,11 +182,4 @@ def Client(version, *args, **kwargs):
python-watcherclient's doc.
"""
api_version, client_class = _get_client_class_and_version(version)
kw_api = kwargs.get('os_infra_optim_api_version')
endpoint = kwargs.get('endpoint')
# If both os_infra_optim_api_version and endpoint are not provided, get
# API version from arg.
if not kw_api and not endpoint:
kwargs['os_infra_optim_api_version'] = api_version.get_string()
return client_class(*args, **kwargs)

View File

@@ -26,44 +26,14 @@ if not LOG.handlers:
LOG.addHandler(logging.StreamHandler())
MINOR_1_START_END_TIMING = '1.1'
MINOR_2_FORCE_AUDIT = '1.2'
MINOR_5_ACTION_UPDATE = '1.5'
HEADER_NAME = "OpenStack-API-Version"
SERVICE_TYPE = "infra-optim"
# key is a deprecated version and value is an alternative version.
DEPRECATED_VERSIONS = {}
_type_error_msg = _("'%(other)s' should be an instance of '%(cls)s'")
def allow_start_end_audit_time(requested_version):
"""Check if we should support optional start/end attributes for Audit.
Version 1.1 of the API added support for start and end time of continuous
audits.
"""
return (APIVersion(requested_version) >=
APIVersion(MINOR_1_START_END_TIMING))
def launch_audit_forced(requested_version):
"""Check if we should support force option for Audit.
Version 1.2 of the API added support for force option.
"""
return (APIVersion(requested_version) >=
APIVersion(MINOR_2_FORCE_AUDIT))
def action_update_supported(requested_version):
"""Check if we should support action update functionality.
Version 1.5 of the API added support for updating action state.
"""
return (APIVersion(requested_version) >=
APIVersion(MINOR_5_ACTION_UPDATE))
class APIVersion(object):
"""This class represents an API Version Request.

View File

@@ -39,9 +39,10 @@ Base utilities to build API operation managers and objects on top of.
import abc
import copy
from urllib import parse
from oslo_utils import strutils
import six
from six.moves.urllib import parse
from watcherclient._i18n import _
from watcherclient.common.apiclient import exceptions
@@ -223,7 +224,8 @@ class BaseManager(HookableMixin):
return self.client.delete(url)
class ManagerWithFind(BaseManager, metaclass=abc.ABCMeta):
@six.add_metaclass(abc.ABCMeta)
class ManagerWithFind(BaseManager):
"""Manager with additional `find()`/`findall()` methods."""
@abc.abstractmethod

View File

@@ -36,6 +36,8 @@ Exception definitions.
import inspect
import sys
import six
from watcherclient._i18n import _
@@ -455,7 +457,7 @@ def from_response(response, method, url):
kwargs["message"] = (error.get("message") or
error.get("faultstring"))
kwargs["details"] = (error.get("details") or
str(body))
six.text_type(body))
elif content_type.startswith("text/"):
kwargs["details"] = response.text

View File

@@ -19,7 +19,8 @@ Base utilities to build API operation managers and objects on top of.
"""
import copy
from urllib import parse as urlparse
import six.moves.urllib.parse as urlparse
from watcherclient.common.apiclient import base
@@ -30,7 +31,10 @@ def getid(obj):
Abstracts the common pattern of allowing both an object or an
object's ID (UUID) as a parameter when dealing with relationships.
"""
return getattr(obj, 'id', obj)
try:
return obj.id
except AttributeError:
return obj
class Manager(object):

View File

@@ -0,0 +1,278 @@
# Copyright 2012 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
# W0603: Using the global statement
# W0621: Redefining name %s from outer scope
# pylint: disable=W0603,W0621
from __future__ import print_function
import getpass
import inspect
import os
import sys
import textwrap
from oslo_utils import encodeutils
from oslo_utils import strutils
import prettytable
import six
from six import moves
from watcherclient._i18n import _
class MissingArgs(Exception):
"""Supplied arguments are not sufficient for calling a function."""
def __init__(self, missing):
self.missing = missing
msg = _("Missing arguments: %s") % ", ".join(missing)
super(MissingArgs, self).__init__(msg)
def validate_args(fn, *args, **kwargs):
"""Check that the supplied args are sufficient for calling a function.
>>> validate_args(lambda a: None)
Traceback (most recent call last):
...
MissingArgs: Missing argument(s): a
>>> validate_args(lambda a, b, c, d: None, 0, c=1)
Traceback (most recent call last):
...
MissingArgs: Missing argument(s): b, d
:param fn: the function to check
:param arg: the positional arguments supplied
:param kwargs: the keyword arguments supplied
"""
argspec = inspect.getargspec(fn)
num_defaults = len(argspec.defaults or [])
required_args = argspec.args[:len(argspec.args) - num_defaults]
def isbound(method):
return getattr(method, '__self__', None) is not None
if isbound(fn):
required_args.pop(0)
missing = [arg for arg in required_args if arg not in kwargs]
missing = missing[len(args):]
if missing:
raise MissingArgs(missing)
def arg(*args, **kwargs):
"""Decorator for CLI args.
Example:
>>> @arg("name", help="Name of the new entity")
... def entity_create(args):
... pass
"""
def _decorator(func):
add_arg(func, *args, **kwargs)
return func
return _decorator
def env(*args, **kwargs):
"""Returns the first environment variable set.
If all are empty, defaults to '' or keyword arg `default`.
"""
for arg in args:
value = os.environ.get(arg)
if value:
return value
return kwargs.get('default', '')
def add_arg(func, *args, **kwargs):
"""Bind CLI arguments to a shell.py `do_foo` function."""
if not hasattr(func, 'arguments'):
func.arguments = []
# NOTE(sirp): avoid dups that can occur when the module is shared across
# tests.
if (args, kwargs) not in func.arguments:
# Because of the semantics of decorator composition if we just append
# to the options list positional options will appear to be backwards.
func.arguments.insert(0, (args, kwargs))
def unauthenticated(func):
"""Adds 'unauthenticated' attribute to decorated function.
Usage:
>>> @unauthenticated
... def mymethod(f):
... pass
"""
func.unauthenticated = True
return func
def isunauthenticated(func):
"""Checks if the function does not require authentication.
Mark such functions with the `@unauthenticated` decorator.
:returns: bool
"""
return getattr(func, 'unauthenticated', False)
def print_list(objs, fields, formatters=None, sortby_index=0,
mixed_case_fields=None, field_labels=None):
"""Print a list or objects as a table, one row per object.
:param objs: iterable of :class:`Resource`
:param fields: attributes that correspond to columns, in order
:param formatters: `dict` of callables for field formatting
:param sortby_index: index of the field for sorting table rows
:param mixed_case_fields: fields corresponding to object attributes that
have mixed case names (e.g., 'serverId')
:param field_labels: Labels to use in the heading of the table, default to
fields.
"""
formatters = formatters or {}
mixed_case_fields = mixed_case_fields or []
field_labels = field_labels or fields
if len(field_labels) != len(fields):
raise ValueError(_("Field labels list %(labels)s has different number "
"of elements than fields list %(fields)s"),
{'labels': field_labels, 'fields': fields})
if sortby_index is None:
kwargs = {}
else:
kwargs = {'sortby': field_labels[sortby_index]}
pt = prettytable.PrettyTable(field_labels)
pt.align = 'l'
for o in objs:
row = []
for field in fields:
data = '-'
if field in formatters:
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, '')
if data is None:
data = '-'
row.append(data)
pt.add_row(row)
if six.PY3:
print(encodeutils.safe_encode(pt.get_string(**kwargs)).decode())
else:
print(encodeutils.safe_encode(pt.get_string(**kwargs)))
def print_dict(dct, dict_property="Property", wrap=0):
"""Print a `dict` as a table of two columns.
:param dct: `dict` to print
:param dict_property: name of the first column
:param wrap: wrapping for the second column
"""
pt = prettytable.PrettyTable([dict_property, 'Value'])
pt.align = 'l'
for k, v in dct.items():
# convert dict to str to check length
if isinstance(v, dict):
v = six.text_type(v)
if wrap > 0:
v = textwrap.fill(six.text_type(v), wrap)
elif wrap < 0:
raise ValueError(_("Wrap argument should be a positive integer"))
# if value has a newline, add in multiple rows
# e.g. fault with stacktrace
if v and isinstance(v, six.string_types) and r'\n' in v:
lines = v.strip().split(r'\n')
col1 = k
for line in lines:
pt.add_row([col1, line])
col1 = ''
else:
if v is None:
v = '-'
pt.add_row([k, v])
if six.PY3:
print(encodeutils.safe_encode(pt.get_string()).decode())
else:
print(encodeutils.safe_encode(pt.get_string()))
def get_password(max_password_prompts=3):
"""Read password from TTY."""
verify = strutils.bool_from_string(env("OS_VERIFY_PASSWORD"))
pw = None
if hasattr(sys.stdin, "isatty") and sys.stdin.isatty():
# Check for Ctrl-D
try:
for __ in moves.range(max_password_prompts):
pw1 = getpass.getpass("OS Password: ")
if verify:
pw2 = getpass.getpass("Please verify: ")
else:
pw2 = pw1
if pw1 == pw2 and pw1:
pw = pw1
break
except EOFError:
pass
return pw
def service_type(stype):
"""Adds 'service_type' attribute to decorated function.
Usage:
.. code-block:: python
@service_type('volume')
def mymethod(f):
...
"""
def inner(f):
f.service_type = stype
return f
return inner
def get_service_type(f):
"""Retrieves service type from function."""
return getattr(f, 'service_type', None)
def pretty_choice_list(l):
return ', '.join("'%s'" % i for i in l)
def exit(msg=''):
if msg:
print(msg, file=sys.stderr)
sys.exit(1)

View File

@@ -18,6 +18,7 @@ import logging
from cliff import command
from cliff import lister
from cliff import show
import six
class CommandMeta(abc.ABCMeta):
@@ -29,7 +30,8 @@ class CommandMeta(abc.ABCMeta):
return super(CommandMeta, mcs).__new__(mcs, name, bases, cls_dict)
class Command(command.Command, metaclass=CommandMeta):
@six.add_metaclass(CommandMeta)
class Command(command.Command):
def run(self, parsed_args):
self.log.debug('run(%s)', parsed_args)

View File

@@ -14,35 +14,36 @@
# under the License.
import copy
from distutils import version
import functools
import hashlib
import http.client
import io
import logging
import os
import re
import socket
import ssl
import textwrap
import time
from urllib import parse as urlparse
from keystoneauth1 import adapter
from keystoneauth1 import exceptions as kexceptions
from oslo_serialization import jsonutils
from oslo_utils import strutils
import requests
import six
from six.moves import http_client
import six.moves.urllib.parse as urlparse
from watcherclient._i18n import _
from watcherclient.common import api_versioning
from watcherclient import exceptions
# Record the latest version that this client was tested with.
DEFAULT_VER = '1.latest'
# Minor version 4 for adding webhook API
LAST_KNOWN_API_VERSION = 5
LATEST_VERSION = '1.{}'.format(LAST_KNOWN_API_VERSION)
# NOTE(deva): Record the latest version that this client was tested with.
# We still have a lot of work to do in the client to implement
# microversion support in the client properly! See
# http://specs.openstack.org/openstack/watcher-specs/specs/kilo/api-microversions.html # noqa
# for full details.
DEFAULT_VER = '1.0'
LOG = logging.getLogger(__name__)
USER_AGENT = 'python-watcherclient'
@@ -62,7 +63,7 @@ SUPPORTED_ENDPOINT_SCHEME = ('http', 'https')
def _trim_endpoint_api_version(url):
"""Trim API version and trailing slash from endpoint."""
return re.sub(f'{API_VERSION}$', '', url.rstrip('/'))
return url.rstrip('/').rstrip(API_VERSION)
def _extract_error_json(body):
@@ -103,8 +104,20 @@ class VersionNegotiationMixin(object):
{'valid': ', '.join(API_VERSION_SELECTED_STATES),
'value': self.api_version_select_state})
min_ver, max_ver = self._parse_version_headers(resp)
# NOTE: servers before commit 32fb6e99 did not return version headers
# on error, so we need to perform a GET to determine
# the supported version range
if not max_ver:
LOG.debug('No version header in response, requesting from server')
if self.os_watcher_api_version:
base_version = ("/v%s" %
str(self.os_watcher_api_version).split('.')[0])
else:
base_version = API_VERSION
resp = self._make_simple_request(conn, 'GET', base_version)
min_ver, max_ver = self._parse_version_headers(resp)
# If the user requested an explicit version or we have negotiated a
# version and still failing then error now. The server could
# version and still failing then error now. The server could
# support the version requested but the requested operation may not
# be supported by the requested version.
if self.api_version_select_state == 'user':
@@ -113,36 +126,33 @@ class VersionNegotiationMixin(object):
"server or the requested operation is not supported by the "
"requested version. Supported version range is %(min)s to "
"%(max)s")
% {'req': self.os_infra_optim_api_version,
% {'req': self.os_watcher_api_version,
'min': min_ver, 'max': max_ver}))
if self.api_version_select_state == 'negotiated':
raise exceptions.UnsupportedVersion(textwrap.fill(
_("No API version was specified and the requested operation "
"was not supported by the client's negotiated API version "
"%(req)s. Supported version range is: %(min)s to %(max)s")
% {'req': self.os_infra_optim_api_version,
% {'req': self.os_watcher_api_version,
'min': min_ver, 'max': max_ver}))
negotiated_ver = api_versioning.APIVersion(
self.os_infra_optim_api_version)
min_ver = api_versioning.APIVersion(min_ver)
max_ver = api_versioning.APIVersion(max_ver)
if negotiated_ver > max_ver:
negotiated_ver = max_ver
negotiated_ver = str(
min(version.StrictVersion(self.os_watcher_api_version),
version.StrictVersion(max_ver)))
if negotiated_ver < min_ver:
negotiated_ver = min_ver
# server handles microversions, but doesn't support
# the requested version, so try a negotiated version
self.api_version_select_state = 'negotiated'
self.os_infra_optim_api_version = negotiated_ver.get_string()
LOG.debug('Negotiated API version is %s', negotiated_ver.get_string())
self.os_watcher_api_version = negotiated_ver
LOG.debug('Negotiated API version is %s', negotiated_ver)
return negotiated_ver
def _generic_parse_version_headers(self, accessor_func):
min_ver = accessor_func('OpenStack-API-Minimum-Version',
min_ver = accessor_func('X-OpenStack-Watcher-API-Minimum-Version',
None)
max_ver = accessor_func('OpenStack-API-Maximum-Version',
max_ver = accessor_func('X-OpenStack-Watcher-API-Maximum-Version',
None)
return min_ver, max_ver
@@ -196,8 +206,8 @@ class HTTPClient(VersionNegotiationMixin):
self.endpoint_trimmed = _trim_endpoint_api_version(endpoint)
self.auth_token = kwargs.get('token')
self.auth_ref = kwargs.get('auth_ref')
self.os_infra_optim_api_version = kwargs.get(
'os_infra_optim_api_version', DEFAULT_VER)
self.os_watcher_api_version = kwargs.get('os_watcher_api_version',
DEFAULT_VER)
self.api_version_select_state = kwargs.get(
'api_version_select_state', 'default')
self.conflict_max_retries = kwargs.pop('max_retries',
@@ -250,7 +260,7 @@ class HTTPClient(VersionNegotiationMixin):
if not self.session.verify:
curl.append('-k')
elif isinstance(self.session.verify, str):
elif isinstance(self.session.verify, six.string_types):
curl.append('--cacert %s' % self.session.verify)
if self.session.cert:
@@ -279,7 +289,7 @@ class HTTPClient(VersionNegotiationMixin):
LOG.debug('\n'.join(dump))
def _make_connection_url(self, url):
return '%s/%s' % (self.endpoint_trimmed.rstrip('/'), url.lstrip('/'))
return urlparse.urljoin(self.endpoint_trimmed, url)
def _parse_version_headers(self, resp):
return self._generic_parse_version_headers(resp.headers.get)
@@ -297,15 +307,9 @@ class HTTPClient(VersionNegotiationMixin):
# Copy the kwargs so we can reuse the original in case of redirects
kwargs['headers'] = copy.deepcopy(kwargs.get('headers', {}))
kwargs['headers'].setdefault('User-Agent', USER_AGENT)
if self.os_infra_optim_api_version:
api_version = api_versioning.get_api_version(
self.os_infra_optim_api_version)
if api_version.is_latest():
api_version = api_versioning.get_api_version(
LATEST_VERSION)
kwargs['headers'].setdefault(
'OpenStack-API-Version',
' '.join(['infra-optim', api_version.get_string()]))
if self.os_watcher_api_version:
kwargs['headers'].setdefault('X-OpenStack-Watcher-API-Version',
self.os_watcher_api_version)
if self.auth_token:
kwargs['headers'].setdefault('X-Auth-Token', self.auth_token)
@@ -328,10 +332,10 @@ class HTTPClient(VersionNegotiationMixin):
# to servers that did not support microversions. Details here:
# http://specs.openstack.org/openstack/watcher-specs/specs/kilo/api-microversions.html#use-case-3b-new-client-communicating-with-a-old-watcher-user-specified # noqa
if resp.status_code == http.client.NOT_ACCEPTABLE:
if resp.status_code == http_client.NOT_ACCEPTABLE:
negotiated_ver = self.negotiate_version(self.session, resp)
kwargs['headers']['OpenStack-API-Version'] = (
' '.join(['infra-optim', negotiated_ver]))
kwargs['headers']['X-OpenStack-Watcher-API-Version'] = (
negotiated_ver)
return self._http_request(url, method, **kwargs)
except requests.exceptions.RequestException as e:
@@ -350,31 +354,23 @@ class HTTPClient(VersionNegotiationMixin):
# Read body into string if it isn't obviously image data
body_str = None
if resp.headers.get('Content-Type') != 'application/octet-stream':
# decoding byte to string is necessary for Python 3 compatibility
# this issues has not been found with Python 3 unit tests
# because the test creates a fake http response of type str
# the if statement satisfies test (str) and real (bytes) behavior
body_list = [
chunk.decode("utf-8") if isinstance(chunk, bytes)
else chunk for chunk in body_iter
]
body_str = ''.join(body_list)
body_str = ''.join([chunk for chunk in body_iter])
self.log_http_response(resp, body_str)
body_iter = io.StringIO(body_str)
body_iter = six.StringIO(body_str)
else:
self.log_http_response(resp)
if resp.status_code >= http.client.BAD_REQUEST:
if resp.status_code >= http_client.BAD_REQUEST:
error_json = _extract_error_json(body_str)
raise exceptions.from_response(
resp, error_json.get('faultstring'),
error_json.get('debuginfo'), method, url)
elif resp.status_code in (http.client.MOVED_PERMANENTLY,
http.client.FOUND,
http.client.USE_PROXY):
elif resp.status_code in (http_client.MOVED_PERMANENTLY,
http_client.FOUND,
http_client.USE_PROXY):
# Redirected. Reissue the request to the new location.
return self._http_request(resp['location'], method, **kwargs)
elif resp.status_code == http.client.MULTIPLE_CHOICES:
elif resp.status_code == http_client.MULTIPLE_CHOICES:
raise exceptions.from_response(resp, method=method, url=url)
return resp, body_iter
@@ -390,9 +386,9 @@ class HTTPClient(VersionNegotiationMixin):
resp, body_iter = self._http_request(url, method, **kwargs)
content_type = resp.headers.get('Content-Type')
if (resp.status_code in (http.client.NO_CONTENT,
http.client.RESET_CONTENT) or
content_type is None):
if (resp.status_code in (http_client.NO_CONTENT,
http_client.RESET_CONTENT)
or content_type is None):
return resp, list()
if 'application/json' in content_type:
@@ -413,7 +409,7 @@ class HTTPClient(VersionNegotiationMixin):
return self._http_request(url, method, **kwargs)
class VerifiedHTTPSConnection(http.client.HTTPSConnection):
class VerifiedHTTPSConnection(six.moves.http_client.HTTPSConnection):
"""httplib-compatible connection using client-side SSL authentication
:see http://code.activestate.com/recipes/
@@ -422,8 +418,9 @@ class VerifiedHTTPSConnection(http.client.HTTPSConnection):
def __init__(self, host, port, key_file=None, cert_file=None,
ca_file=None, timeout=None, insecure=False):
super(VerifiedHTTPSConnection, self).__init__(
self, host, port, key_file=key_file, cert_file=cert_file)
six.moves.http_client.HTTPSConnection.__init__(self, host, port,
key_file=key_file,
cert_file=cert_file)
self.key_file = key_file
self.cert_file = cert_file
if ca_file is not None:
@@ -437,6 +434,11 @@ class VerifiedHTTPSConnection(http.client.HTTPSConnection):
"""Connect to a host on a given (SSL) port.
If ca_file is pointing somewhere, use it to check Server Certificate.
Redefined/copied and extended from httplib.py:1105 (Python 2.6.x).
This is needed to pass cert_reqs=ssl.CERT_REQUIRED as parameter to
ssl.wrap_socket(), which forces SSL to check server certificate against
our client certificate.
"""
sock = socket.create_connection((self.host, self.port), self.timeout)
@@ -444,21 +446,17 @@ class VerifiedHTTPSConnection(http.client.HTTPSConnection):
self.sock = sock
self._tunnel()
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
if self.insecure is True:
context.check_hostname = False
context.verify_mode = ssl.CERT_NONE
kwargs = {'cert_reqs': ssl.CERT_NONE}
else:
context.load_verify_locations(self.ca_file)
kwargs = {'cert_reqs': ssl.CERT_REQUIRED, 'ca_certs': self.ca_file}
if self.cert_file:
kwargs['certfile'] = self.cert_file
if self.key_file:
context.load_cert_chain(self.cert_file, self.key_file)
else:
context.load_cert_chain(self.cert_file)
kwargs['keyfile'] = self.key_file
self.sock = context.wrap_socket(sock)
self.sock = ssl.wrap_socket(sock, **kwargs)
@staticmethod
def get_system_ca_file():
@@ -479,13 +477,13 @@ class SessionClient(VersionNegotiationMixin, adapter.LegacyJsonAdapter):
"""HTTP client based on Keystone client session."""
def __init__(self,
os_infra_optim_api_version,
os_watcher_api_version,
api_version_select_state,
max_retries,
retry_interval,
endpoint,
**kwargs):
self.os_infra_optim_api_version = os_infra_optim_api_version
self.os_watcher_api_version = os_watcher_api_version
self.api_version_select_state = api_version_select_state
self.conflict_max_retries = max_retries
self.conflict_retry_interval = retry_interval
@@ -504,22 +502,15 @@ class SessionClient(VersionNegotiationMixin, adapter.LegacyJsonAdapter):
def _http_request(self, url, method, **kwargs):
kwargs.setdefault('user_agent', USER_AGENT)
kwargs.setdefault('auth', self.auth)
if isinstance(self.endpoint_override, str):
if isinstance(self.endpoint_override, six.string_types):
kwargs.setdefault(
'endpoint_override',
_trim_endpoint_api_version(self.endpoint_override)
)
if getattr(self, 'os_infra_optim_api_version', None):
api_version = api_versioning.get_api_version(
self.os_infra_optim_api_version)
if api_version.is_latest():
api_version = api_versioning.get_api_version(
LATEST_VERSION)
kwargs['headers'].setdefault(
'OpenStack-API-Version',
' '.join(['infra-optim',
api_version.get_string()]))
if getattr(self, 'os_watcher_api_version', None):
kwargs['headers'].setdefault('X-OpenStack-Watcher-API-Version',
self.os_watcher_api_version)
endpoint_filter = kwargs.setdefault('endpoint_filter', {})
endpoint_filter.setdefault('interface', self.interface)
@@ -528,22 +519,22 @@ class SessionClient(VersionNegotiationMixin, adapter.LegacyJsonAdapter):
resp = self.session.request(url, method,
raise_exc=False, **kwargs)
if resp.status_code == http.client.NOT_ACCEPTABLE:
if resp.status_code == http_client.NOT_ACCEPTABLE:
negotiated_ver = self.negotiate_version(self.session, resp)
kwargs['headers']['OpenStack-API-Version'] = (
' '.join(['infra-optim', negotiated_ver]))
kwargs['headers']['X-OpenStack-Watcher-API-Version'] = (
negotiated_ver)
return self._http_request(url, method, **kwargs)
if resp.status_code >= http.client.BAD_REQUEST:
if resp.status_code >= http_client.BAD_REQUEST:
error_json = _extract_error_json(resp.content)
raise exceptions.from_response(
resp, error_json.get('faultstring'),
error_json.get('debuginfo'), method, url)
elif resp.status_code in (http.client.MOVED_PERMANENTLY,
http.client.FOUND, http.client.USE_PROXY):
elif resp.status_code in (http_client.MOVED_PERMANENTLY,
http_client.FOUND, http_client.USE_PROXY):
# Redirected. Reissue the request to the new location.
location = resp.headers.get('location')
resp = self._http_request(location, method, **kwargs)
elif resp.status_code == http.client.MULTIPLE_CHOICES:
elif resp.status_code == http_client.MULTIPLE_CHOICES:
raise exceptions.from_response(resp, method=method, url=url)
return resp
@@ -559,7 +550,7 @@ class SessionClient(VersionNegotiationMixin, adapter.LegacyJsonAdapter):
body = resp.content
content_type = resp.headers.get('content-type', None)
status = resp.status_code
if (status in (http.client.NO_CONTENT, http.client.RESET_CONTENT) or
if (status in (http_client.NO_CONTENT, http_client.RESET_CONTENT) or
content_type is None):
return resp, list()
if 'application/json' in content_type:
@@ -583,7 +574,7 @@ def _construct_http_client(endpoint=None,
session=None,
token=None,
auth_ref=None,
os_infra_optim_api_version=DEFAULT_VER,
os_watcher_api_version=DEFAULT_VER,
api_version_select_state='default',
max_retries=DEFAULT_MAX_RETRIES,
retry_interval=DEFAULT_RETRY_INTERVAL,
@@ -614,29 +605,27 @@ def _construct_http_client(endpoint=None,
'the session to construct a client: %s',
', '.join(dvars))
return SessionClient(
session=session,
os_infra_optim_api_version=os_infra_optim_api_version,
api_version_select_state=api_version_select_state,
max_retries=max_retries,
retry_interval=retry_interval,
endpoint=endpoint,
**kwargs)
return SessionClient(session=session,
os_watcher_api_version=os_watcher_api_version,
api_version_select_state=api_version_select_state,
max_retries=max_retries,
retry_interval=retry_interval,
endpoint=endpoint,
**kwargs)
else:
if kwargs:
LOG.warning('The following arguments are being ignored when '
'constructing the client: %s', ', '.join(kwargs))
return HTTPClient(
endpoint=endpoint,
token=token,
auth_ref=auth_ref,
os_infra_optim_api_version=os_infra_optim_api_version,
api_version_select_state=api_version_select_state,
max_retries=max_retries,
retry_interval=retry_interval,
timeout=timeout,
ca_file=ca_file,
cert_file=cert_file,
key_file=key_file,
insecure=insecure)
return HTTPClient(endpoint=endpoint,
token=token,
auth_ref=auth_ref,
os_watcher_api_version=os_watcher_api_version,
api_version_select_state=api_version_select_state,
max_retries=max_retries,
retry_interval=retry_interval,
timeout=timeout,
ca_file=ca_file,
cert_file=cert_file,
key_file=key_file,
insecure=insecure)

View File

@@ -14,6 +14,8 @@
# License for the specific language governing permissions and limitations
# under the License.
from __future__ import print_function
import argparse
import os
import uuid

23
watcherclient/config.py Normal file
View File

@@ -0,0 +1,23 @@
# -*- encoding: utf-8 -*-
# Copyright (c) 2018 Servionica
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from oslo_config import cfg
service_option = cfg.BoolOpt("watcher_client",
default=True,
help="Whether or not watcher_client is expected"
"to be available")

View File

@@ -71,7 +71,6 @@ class AmbiguousAuthSystem(exceptions.ClientException):
"""Could not obtain token and endpoint using provided credentials."""
pass
# Alias for backwards compatibility
AmbigiousAuthSystem = AmbiguousAuthSystem

View File

@@ -15,14 +15,9 @@ import logging
from osc_lib import utils
import watcherclient
from watcherclient.common import api_versioning
from watcherclient.common import httpclient
from watcherclient import exceptions
LOG = logging.getLogger(__name__)
DEFAULT_API_VERSION = httpclient.LATEST_VERSION
DEFAULT_API_VERSION = '1'
API_VERSION_OPTION = 'os_infra_optim_api_version'
API_NAME = 'infra-optim'
API_VERSIONS = {
@@ -32,17 +27,14 @@ API_VERSIONS = {
def make_client(instance):
"""Returns an infra-optim service client."""
version = api_versioning.APIVersion(instance._api_version[API_NAME])
infraoptim_client_class = utils.get_client_class(
API_NAME,
version.ver_major,
instance._api_version[API_NAME],
API_VERSIONS)
LOG.debug('Instantiating infraoptim client: %s', infraoptim_client_class)
client = infraoptim_client_class(
os_infra_optim_api_version=instance._api_version[API_NAME],
os_watcher_api_version=instance._api_version[API_NAME],
session=instance.session,
region_name=instance._region_name,
)
@@ -61,30 +53,3 @@ def build_option_parser(parser):
DEFAULT_API_VERSION +
' (Env: OS_INFRA_OPTIM_API_VERSION)'))
return parser
def check_api_version(check_version):
"""Validate version supplied by user
Returns:
* True if version is OK
* False if the version has not been checked and the previous plugin
check should be performed
* throws an exception if the version is no good
"""
infra_api_version = api_versioning.get_api_version(check_version)
# Bypass X.latest format microversion
if not infra_api_version.is_latest():
if infra_api_version > api_versioning.APIVersion("2.0"):
if not infra_api_version.matches(
watcherclient.API_MIN_VERSION,
watcherclient.API_MAX_VERSION,
):
msg = "versions supported by client: %(min)s - %(max)s" % {
"min": watcherclient.API_MIN_VERSION.get_string(),
"max": watcherclient.API_MAX_VERSION.get_string(),
}
raise exceptions.CommandError(msg)
return True

34
watcherclient/plugin.py Normal file
View File

@@ -0,0 +1,34 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
from tempest.test_discover import plugins
from watcherclient import config as watcher_config
class WatcherClientTempestPlugin(plugins.TempestPlugin):
def load_tests(self):
base_path = os.path.split(os.path.dirname(
os.path.abspath(__file__)))[0]
test_dir = "watcherclient/tests/functional/v1"
full_test_dir = os.path.join(base_path, test_dir)
return full_test_dir, base_path
def register_opts(self, conf):
conf.register_opt(watcher_config.service_option,
group='service_available')
def get_opt_lists(self):
return [('service_available', [watcher_config.service_option])]

View File

@@ -39,7 +39,6 @@ API_NAME = 'infra-optim'
API_VERSIONS = {
'1': 'watcherclient.v1.client.Client',
}
DEFAULT_OS_INFRA_OPTIM_API_VERSION = '1.latest'
_DEFAULT_IDENTITY_API_VERSION = '3'
_IDENTITY_API_VERSION_2 = ['2', '2.0']
_IDENTITY_API_VERSION_3 = ['3']
@@ -48,6 +47,8 @@ _IDENTITY_API_VERSION_3 = ['3']
class WatcherShell(app.App):
"""Watcher command line interface."""
log = logging.getLogger(__name__)
def __init__(self, **kwargs):
self.client = None
@@ -148,13 +149,11 @@ class WatcherShell(app.App):
metavar='<auth-token>',
default=utils.env('OS_AUTH_TOKEN'),
help='Defaults to env[OS_AUTH_TOKEN].')
parser.add_argument(
'--os-infra-optim-api-version',
metavar='<infra-optim-api-version>',
default=utils.env('OS_INFRA_OPTIM_API_VERSION',
default=DEFAULT_OS_INFRA_OPTIM_API_VERSION),
help='Accepts X, X.Y (where X is major and Y is minor part) or '
'"X.latest", defaults to env[OS_INFRA_OPTIM_API_VERSION].')
parser.add_argument('--os-watcher-api-version',
metavar='<os-watcher-api-version>',
default=utils.env('OS_WATCHER_API_VERSION',
default='1'),
help='Defaults to env[OS_WATCHER_API_VERSION].')
parser.add_argument('--os-endpoint-type',
default=utils.env('OS_ENDPOINT_TYPE'),
help='Defaults to env[OS_ENDPOINT_TYPE] or '
@@ -195,20 +194,17 @@ class WatcherShell(app.App):
except Exception as e:
if not logging.getLogger('').handlers:
logging.basicConfig()
LOG.error('Exception raised: %s', str(e))
self.log.error('Exception raised: %s', str(e))
return ret_val
finally:
LOG.info("END return value: %s", ret_val)
self.log.info("END return value: %s", ret_val)
def main(argv=None):
if argv is None:
argv = sys.argv[1:]
def main(argv=sys.argv[1:]):
watcher_app = WatcherShell()
return watcher_app.run(argv)
if __name__ == '__main__': # pragma: no cover
sys.exit(main(sys.argv[1:]))

View File

@@ -0,0 +1,61 @@
..
Except where otherwise noted, this document is licensed under Creative
Commons Attribution 3.0 License. You can view the license at:
https://creativecommons.org/licenses/by/3.0/
=======
Testing
=======
.. _functional_tests:
Functional tests
================
The following procedure gets you started with Tempest testing but you can also
refer to the `Tempest documentation`_ for more details.
.. _Tempest documentation: https://docs.openstack.org/tempest/latest/
Tempest installation
--------------------
You need to install virtualenv, create a virtual environment and activate it::
$ pip install virtualenv
$ virtualenv watcher-env
$ . watcher-env/bin/activate
Then, to install Tempest you can issue the following commands::
$ git clone https://github.com/openstack/tempest/
$ pip install tempest/
There should be set environment variables using the OpenStack RC file. If
you don't have RC file yet, create ``admin-openrc`` file and fill it using
the following example::
export OS_PROJECT_DOMAIN_NAME=default
export OS_USER_DOMAIN_NAME=default
export OS_PROJECT_NAME=admin
export OS_USERNAME=admin
export OS_PASSWORD=admin
export OS_AUTH_URL=http://controller:35357/v3
export OS_IDENTITY_API_VERSION=3
export OS_IMAGE_API_VERSION=2
Then, save file and execute ``source admin-openrc`` to set environment
variables.
To run functional tests you need to go to python-watcherclient folder, install
all requirements and execute ``tempest run`` command::
$ pip install -r requirements.txt -r test-requirements.txt
$ pip install .
$ tempest run --regex watcherclient.tests.functional
You can run specified test(s) by using regular expression::
$ tempest run --regex watcherclient.tests.functional.v1.test_action.ActionTests.test_action_list

View File

@@ -1,220 +0,0 @@
# Copyright (c) 2016 Servionica
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from oslo_utils import uuidutils
import functools
from tempest.lib.common.utils import test_utils
from watcherclient.tests.client_functional.v1 import base
class ActionTests(base.TestCase):
"""Functional tests for action."""
dummy_name = 'dummy'
list_fields = ['UUID', 'Parents', 'State', 'Action Plan', 'Action']
detailed_list_fields = list_fields + ['Created At', 'Updated At',
'Deleted At', 'Parameters']
audit_template_name = 'a' + uuidutils.generate_uuid()
audit_uuid = None
@classmethod
def setUpClass(cls):
template_raw_output = cls.watcher(
'audittemplate create %s dummy -s dummy' % cls.audit_template_name)
template_output = cls.parse_show_as_object(template_raw_output)
audit_output = cls.parse_show_as_object(cls.watcher(
'audit create -a %s' % template_output['Name']))
cls.audit_uuid = audit_output['UUID']
audit_created = test_utils.call_until_true(
func=functools.partial(cls.has_audit_created, cls.audit_uuid),
duration=600,
sleep_for=2)
if not audit_created:
raise Exception('Audit has not been succeeded')
@classmethod
def tearDownClass(cls):
# Delete Action Plan and all related actions.
output = cls.parse_show(
cls.watcher('actionplan list --audit %s' % cls.audit_uuid))
action_plan_uuid = list(output[0])[0]
raw_output = cls.watcher('actionplan delete %s' % action_plan_uuid)
cls.assertOutput('', raw_output)
# Delete audit
raw_output = cls.watcher('audit delete %s' % cls.audit_uuid)
cls.assertOutput('', raw_output)
# Delete Template
raw_output = cls.watcher(
'audittemplate delete %s' % cls.audit_template_name)
cls.assertOutput('', raw_output)
def test_action_list(self):
raw_output = self.watcher('action list')
self.assert_table_structure([raw_output], self.list_fields)
def test_action_detailed_list(self):
raw_output = self.watcher('action list --detail')
self.assert_table_structure([raw_output], self.detailed_list_fields)
def test_action_show(self):
action_list = self.parse_show(self.watcher('action list --audit %s'
% self.audit_uuid))
action_uuid = list(action_list[0])[0]
action = self.watcher('action show %s' % action_uuid)
self.assertIn(action_uuid, action)
self.assert_table_structure([action],
self.detailed_list_fields)
class ActionUpdateTests(base.TestCase):
"""Functional tests for action update functionality."""
# Use API version 1.5 for action update tests
api_version = 1.5
dummy_name = 'dummy'
audit_template_name = 'b' + uuidutils.generate_uuid()
audit_uuid = None
action_uuid = None
@classmethod
def setUpClass(cls):
# Create audit template
template_raw_output = cls.watcher(
'audittemplate create %s dummy -s dummy' % cls.audit_template_name)
template_output = cls.parse_show_as_object(template_raw_output)
# Create audit
audit_output = cls.parse_show_as_object(cls.watcher(
'audit create -a %s' % template_output['Name']))
cls.audit_uuid = audit_output['UUID']
# Wait for audit to complete
audit_created = test_utils.call_until_true(
func=functools.partial(cls.has_audit_created, cls.audit_uuid),
duration=600,
sleep_for=2)
if not audit_created:
raise Exception('Audit has not been succeeded')
# Get an action to test updates on
action_list = cls.parse_show(cls.watcher('action list --audit %s'
% cls.audit_uuid))
if action_list:
cls.action_uuid = list(action_list[0])[0]
@classmethod
def tearDownClass(cls):
# Clean up: Delete Action Plan and all related actions
if cls.audit_uuid:
output = cls.parse_show(
cls.watcher('actionplan list --audit %s' % cls.audit_uuid))
if output:
action_plan_uuid = list(output[0])[0]
raw_output = cls.watcher(
'actionplan delete %s' % action_plan_uuid)
cls.assertOutput('', raw_output)
# Delete audit
raw_output = cls.watcher('audit delete %s' % cls.audit_uuid)
cls.assertOutput('', raw_output)
# Delete template
raw_output = cls.watcher(
'audittemplate delete %s' % cls.audit_template_name)
cls.assertOutput('', raw_output)
def test_action_update_with_state_and_reason(self):
"""Test updating action state with reason using API 1.5"""
if not self.action_uuid:
self.skipTest("No actions available for testing")
# Update action state to SKIPPED with reason
raw_output = self.watcher(
'action update --state SKIPPED --reason "Functional test skip" %s'
% self.action_uuid)
# Verify the action was updated
action = self.parse_show_as_object(
self.watcher('action show %s' % self.action_uuid))
self.assertEqual('SKIPPED', action['State'])
self.assertEqual('Action skipped by user. Reason: Functional test '
'skip', action['Status Message'])
# Verify output contains the action UUID
self.assertIn(self.action_uuid, raw_output)
def test_action_update_with_state_only(self):
"""Test updating action state without reason"""
if not self.action_uuid:
self.skipTest("No actions available for testing")
# Update action state to SKIPPED without reason
raw_output = self.watcher(
'action update --state SKIPPED %s' % self.action_uuid)
# Verify the action was updated
action = self.parse_show_as_object(
self.watcher('action show %s' % self.action_uuid))
self.assertEqual('SKIPPED', action['State'])
# Verify output contains the action UUID
self.assertIn(self.action_uuid, raw_output)
def test_action_update_missing_state_fails(self):
"""Test that action update fails when no state is provided"""
if not self.action_uuid:
self.skipTest("No actions available for testing")
# This should fail because --state is required
raw_output = self.watcher(
'action update %s' % self.action_uuid, fail_ok=True)
# Should contain error message about missing state
self.assertIn(
'At least one field update is required for this operation',
raw_output)
def test_action_update_nonexistent_action_fails(self):
"""Test that action update fails for non-existent action"""
fake_uuid = uuidutils.generate_uuid()
# This should fail because the action doesn't exist
raw_output = self.watcher(
'action update --state SKIPPED %s' % fake_uuid, fail_ok=True)
# Should contain error message about action not found
self.assertIn('404', raw_output)
class ActionUpdateApiVersionTests(base.TestCase):
"""Test action update functionality with different API versions."""
# Use API version 1.0 to test version checking
api_version = 1.0
def test_action_update_unsupported_api_version(self):
"""Test that action update fails with API version < 1.5"""
fake_uuid = uuidutils.generate_uuid()
# This should fail because API version 1.0 doesn't support updates
raw_output = self.watcher(
'action update --state SKIPPED %s' % fake_uuid, fail_ok=True)
# Should contain error message about unsupported API version
self.assertIn('not supported in API version', raw_output)
self.assertIn('Minimum required version is 1.5', raw_output)

View File

@@ -17,24 +17,35 @@ import shlex
import subprocess
import testtools
import six
from tempest import clients
from tempest.common import credentials_factory as creds_factory
from tempest.lib.cli import output_parser
from tempest.lib import exceptions
def credentials():
# You can get credentials from OS environment.
# You can get credentials either from tempest.conf file or
# from OS environment.
tempest_creds = clients.get_auth_provider(
creds_factory.get_configured_admin_credentials())
creds = tempest_creds.credentials
creds_dict = {
'--os-username': os.environ.get('OS_USERNAME'),
'--os-password': os.environ.get('OS_PASSWORD'),
'--os-project-name': os.environ.get('OS_PROJECT_NAME'),
'--os-auth-url': os.environ.get('OS_AUTH_URL'),
'--os-project-domain-name': os.environ.get('OS_PROJECT_DOMAIN_ID'),
'--os-user-domain-name': os.environ.get('OS_USER_DOMAIN_ID'),
'--os-username': os.environ.get('OS_USERNAME', creds.username),
'--os-password': os.environ.get('OS_PASSWORD', creds.password),
'--os-project-name': os.environ.get('OS_PROJECT_NAME',
creds.project_name),
'--os-auth-url': os.environ.get('OS_AUTH_URL',
tempest_creds.auth_url),
'--os-project-domain-name': os.environ.get('OS_PROJECT_DOMAIN_ID',
creds.project_domain_name),
'--os-user-domain-name': os.environ.get('OS_USER_DOMAIN_ID',
creds.user_domain_name),
}
return [x for sub in creds_dict.items() for x in sub]
def execute(cmd, fail_ok=False, merge_stderr=True):
def execute(cmd, fail_ok=False, merge_stderr=False):
"""Executes specified command for the given action."""
cmdlist = shlex.split(cmd)
cmdlist.extend(credentials())
@@ -51,16 +62,12 @@ def execute(cmd, fail_ok=False, merge_stderr=True):
class TestCase(testtools.TestCase):
delimiter_line = re.compile(r'^\+\-[\+\-]+\-\+$')
api_version = 1.0
delimiter_line = re.compile('^\+\-[\+\-]+\-\+$')
@classmethod
def watcher(cls, cmd, fail_ok=False):
"""Executes watcherclient command for the given action."""
return execute(
'openstack optimize --os-infra-optim-api-version {0} {1}'.format(
cls.api_version, cmd), fail_ok=fail_ok)
return execute('watcher {0}'.format(cmd), fail_ok=fail_ok)
@classmethod
def get_opts(cls, fields, format='value'):
@@ -86,7 +93,7 @@ class TestCase(testtools.TestCase):
def assert_show_fields(self, items, field_names):
"""Verify that all items have keys listed in field_names."""
for item in items:
for key in item.keys():
for key in six.iterkeys(item):
self.assertIn(key, field_names)
def assert_show_structure(self, items, field_names):
@@ -133,9 +140,5 @@ class TestCase(testtools.TestCase):
@classmethod
def has_audit_created(cls, audit_uuid):
audit = cls.parse_show_as_object(
cls.watcher('audit show %s' % audit_uuid))
if audit['Audit Type'] == 'ONESHOT':
return audit['State'] == 'SUCCEEDED'
else:
return audit['State'] == 'ONGOING'
return cls.parse_show_as_object(
cls.watcher('audit show %s' % audit_uuid))['State'] == 'SUCCEEDED'

View File

@@ -0,0 +1,81 @@
# Copyright (c) 2016 Servionica
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from oslo_utils import uuidutils
import functools
from tempest.lib.common.utils import test_utils
from watcherclient.tests.functional.v1 import base
class ActionTests(base.TestCase):
"""Functional tests for action."""
dummy_name = 'dummy'
list_fields = ['UUID', 'Parents', 'State', 'Action Plan', 'Action']
detailed_list_fields = list_fields + ['Created At', 'Updated At',
'Deleted At', 'Parameters']
audit_template_name = 'a' + uuidutils.generate_uuid()
audit_uuid = None
@classmethod
def setUpClass(cls):
template_raw_output = cls.watcher(
'audittemplate create %s dummy -s dummy' % cls.audit_template_name)
template_output = cls.parse_show_as_object(template_raw_output)
audit_output = cls.parse_show_as_object(cls.watcher(
'audit create -a %s' % template_output['Name']))
cls.audit_uuid = audit_output['UUID']
audit_created = test_utils.call_until_true(
func=functools.partial(cls.has_audit_created, cls.audit_uuid),
duration=600,
sleep_for=2)
if not audit_created:
raise Exception('Audit has not been succeeded')
@classmethod
def tearDownClass(cls):
# Delete Action Plan and all related actions.
output = cls.parse_show(
cls.watcher('actionplan list --audit %s' % cls.audit_uuid))
action_plan_uuid = list(output[0])[0]
raw_output = cls.watcher('actionplan delete %s' % action_plan_uuid)
cls.assertOutput('', raw_output)
# Delete audit
raw_output = cls.watcher('audit delete %s' % cls.audit_uuid)
cls.assertOutput('', raw_output)
# Delete Template
raw_output = cls.watcher(
'audittemplate delete %s' % cls.audit_template_name)
cls.assertOutput('', raw_output)
def test_action_list(self):
raw_output = self.watcher('action list')
self.assert_table_structure([raw_output], self.list_fields)
def test_action_detailed_list(self):
raw_output = self.watcher('action list --detail')
self.assert_table_structure([raw_output], self.detailed_list_fields)
def test_action_show(self):
action_list = self.parse_show(self.watcher('action list --audit %s'
% self.audit_uuid))
action_uuid = list(action_list[0])[0]
action = self.watcher('action show %s' % action_uuid)
self.assertIn(action_uuid, action)
self.assert_table_structure([action],
self.detailed_list_fields)

View File

@@ -19,7 +19,7 @@ import functools
from tempest.lib.common.utils import test_utils
from watcherclient.tests.client_functional.v1 import base
from watcherclient.tests.functional.v1 import base
class ActionPlanTests(base.TestCase):

View File

@@ -13,14 +13,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from datetime import datetime
from dateutil import tz
from oslo_utils import uuidutils
import functools
from oslo_utils import uuidutils
from tempest.lib.common.utils import test_utils
from watcherclient.tests.client_functional.v1 import base
from watcherclient.tests.functional.v1 import base
class AuditTests(base.TestCase):
@@ -80,58 +79,6 @@ class AuditTests(base.TestCase):
assert int(audit_output['Interval']) == 2
class AuditTestsV11(AuditTests):
"""This class tests v1.1 of Watcher API"""
api_version = 1.1
detailed_list_fields = AuditTests.list_fields + [
'Created At', 'Updated At', 'Deleted At', 'Parameters', 'Interval',
'Audit Scope', 'Next Run Time', 'Hostname', 'Start Time', 'End Time']
def test_audit_detailed_list(self):
raw_output = self.watcher('audit list --detail')
self.assert_table_structure([raw_output], self.detailed_list_fields)
def test_audit_show(self):
audit = self.watcher('audit show ' + self.audit_uuid)
self.assertIn(self.audit_uuid, audit)
self.assert_table_structure([audit], self.detailed_list_fields)
def test_audit_update(self):
local_time = datetime.now(tz.tzlocal())
local_time_str = local_time.strftime("%Y-%m-%dT%H:%M:%S")
utc_time = (local_time - local_time.utcoffset())
utc_time_str = utc_time.strftime("%Y-%m-%dT%H:%M:%S")
audit_raw_output = self.watcher(
'audit update {0} replace end_time="{1}"'.format(self.audit_uuid,
local_time_str))
audit_output = self.parse_show_as_object(audit_raw_output)
assert audit_output['End Time'] == utc_time_str
class AuditTestsV12(AuditTestsV11):
"""This class tests v1.2 of Watcher API"""
api_version = 1.2
@classmethod
def setUpClass(cls):
raw_output = cls.watcher('audittemplate create %s dummy -s dummy'
% cls.audit_template_name)
template_output = cls.parse_show_as_object(raw_output)
audit_raw_output = cls.watcher(
'audit create --force -a %s' % template_output['Name'])
audit_output = cls.parse_show_as_object(audit_raw_output)
cls.audit_uuid = audit_output['UUID']
audit_created = test_utils.call_until_true(
func=functools.partial(cls.has_audit_created, cls.audit_uuid),
duration=600,
sleep_for=2)
if not audit_created:
raise Exception('Audit has not been succeeded')
class AuditActiveTests(base.TestCase):
list_fields = ['UUID', 'Name', 'Audit Type', 'State', 'Goal', 'Strategy']
@@ -167,13 +114,13 @@ class AuditActiveTests(base.TestCase):
self.watcher('actionplan delete %s' % action_plan_uuid)
self.watcher('audit delete %s' % audit_uuid)
def test_create_oneshot_audit(self):
def test_create_audit(self):
audit = self.watcher('audit create -a %s' % self.audit_template_name)
audit_uuid = self.parse_show_as_object(audit)['UUID']
self.assert_table_structure([audit], self.detailed_list_fields)
self._delete_audit(audit_uuid)
def test_delete_oneshot_audit(self):
def test_delete_audit(self):
audit_uuid = self._create_audit()
self.assertTrue(test_utils.call_until_true(
func=functools.partial(
@@ -187,26 +134,3 @@ class AuditActiveTests(base.TestCase):
self.watcher('actionplan list --audit %s' % audit_uuid))
action_plan_uuid = list(output[0])[0]
self.watcher('actionplan delete %s' % action_plan_uuid)
def test_continuous_audit(self):
audit = self.watcher('audit create -a %s -t CONTINUOUS -i 600'
% self.audit_template_name)
audit_uuid = self.parse_show_as_object(audit)['UUID']
self.assert_table_structure([audit], self.detailed_list_fields)
self.assertTrue(test_utils.call_until_true(
func=functools.partial(
self.has_audit_created, audit_uuid),
duration=600,
sleep_for=2
))
audit_state = self.parse_show_as_object(
self.watcher('audit show %s' % audit_uuid))['State']
if audit_state == 'ONGOING':
self.watcher('audit update %s replace state=CANCELLED'
% audit_uuid)
raw_output = self.watcher('audit delete %s' % audit_uuid)
self.assertOutput('', raw_output)
outputs = self.parse_listing(
self.watcher('actionplan list --audit %s' % audit_uuid))
for actionplan in outputs:
self.watcher('actionplan delete %s' % actionplan['UUID'])

View File

@@ -15,7 +15,7 @@
from oslo_utils import uuidutils
from watcherclient.tests.client_functional.v1 import base
from watcherclient.tests.functional.v1 import base
class AuditTemplateTests(base.TestCase):

View File

@@ -13,7 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from watcherclient.tests.client_functional.v1 import base
from watcherclient.tests.functional.v1 import base
class GoalTests(base.TestCase):

View File

@@ -13,7 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from watcherclient.tests.client_functional.v1 import base
from watcherclient.tests.functional.v1 import base
class ScoringEngineTests(base.TestCase):

View File

@@ -13,7 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from watcherclient.tests.client_functional.v1 import base
from watcherclient.tests.functional.v1 import base
class ServiceTests(base.TestCase):

View File

@@ -13,7 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from watcherclient.tests.client_functional.v1 import base
from watcherclient.tests.functional.v1 import base
class StrategyTests(base.TestCase):

View File

@@ -13,7 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
from unittest import mock
import mock
from watcherclient.common import api_versioning
from watcherclient import exceptions
@@ -148,22 +148,3 @@ class GetAPIVersionTestCase(utils.BaseTestCase):
self.assertEqual(mock_apiversion.return_value,
api_versioning.get_api_version(version))
mock_apiversion.assert_called_once_with(version)
class APIVersionFunctionsTestCase(utils.BaseTestCase):
def test_action_update_supported_true(self):
# Test versions >= 1.5 support action update
self.assertTrue(api_versioning.action_update_supported("1.5"))
self.assertTrue(api_versioning.action_update_supported("1.6"))
self.assertTrue(api_versioning.action_update_supported("2.0"))
def test_action_update_supported_false(self):
# Test versions < 1.5 do not support action update
self.assertFalse(api_versioning.action_update_supported("1.0"))
self.assertFalse(api_versioning.action_update_supported("1.1"))
self.assertFalse(api_versioning.action_update_supported("1.4"))
def test_action_update_supported_edge_case(self):
# Test exact boundary
self.assertTrue(api_versioning.action_update_supported("1.5"))
self.assertFalse(api_versioning.action_update_supported("1.4"))

View File

@@ -10,7 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
from unittest import mock
import mock
from keystoneauth1 import loading as kaloading
@@ -118,7 +118,7 @@ class ClientTest(utils.BaseTestCase):
'os_password': 'PASSWORD',
'os_auth_url': 'http://localhost:35357/v2.0',
'os_auth_token': '',
'os_infra_optim_api_version': "latest",
'os_watcher_api_version': "latest",
}
self._test_get_client(**kwargs)
@@ -129,7 +129,7 @@ class ClientTest(utils.BaseTestCase):
'os_password': 'PASSWORD',
'os_auth_url': 'http://localhost:35357/v2.0',
'os_auth_token': '',
'os_infra_optim_api_version': "1.4",
'os_watcher_api_version': "1.4",
}
self._test_get_client(**kwargs)
@@ -267,7 +267,7 @@ class ClientTest(utils.BaseTestCase):
kwargs = {
'watcher_url': 'http://watcher.example.org:9322/',
'os_auth_token': 'USER_AUTH_TOKEN',
'os_infra_optim_api_version': 'latest',
'os_watcher_api_version': 'latest',
'insecure': True,
'max_retries': 10,
'retry_interval': 10,
@@ -277,7 +277,7 @@ class ClientTest(utils.BaseTestCase):
mock_client.assert_called_once_with(
'1', 'http://watcher.example.org:9322/',
**{
'os_infra_optim_api_version': 'latest',
'os_watcher_api_version': 'latest',
'max_retries': 10,
'retry_interval': 10,
'token': 'USER_AUTH_TOKEN',
@@ -309,7 +309,7 @@ class ClientTest(utils.BaseTestCase):
mock_client.assert_called_once_with(
'1', session.get_endpoint.return_value,
**{
'os_infra_optim_api_version': None,
'os_watcher_api_version': None,
'max_retries': None,
'retry_interval': None,
'session': session,
@@ -328,7 +328,7 @@ class ClientTest(utils.BaseTestCase):
mock_client.assert_called_once_with(
'1', session.get_endpoint.return_value,
**{
'os_infra_optim_api_version': None,
'os_watcher_api_version': None,
'max_retries': None,
'retry_interval': None,
'session': session,
@@ -350,16 +350,3 @@ class ClientTest(utils.BaseTestCase):
client = httpclient.HTTPClient('http://localhost/')
header_redact = client._process_header(name, value)
self.assertEqual(header, header_redact)
def test_make_connection_url(self):
endpoint = 'http://localhost/infra-optim'
url = '/v1/goals'
expected_url = 'http://localhost/infra-optim/v1/goals'
client = httpclient.HTTPClient(endpoint)
conn_url = client._make_connection_url(url)
self.assertEqual(expected_url, conn_url)
def test_port_ends_with_one(self):
endpoint = "http://localhost:8081/"
http_client = httpclient.HTTPClient(endpoint)
self.assertEqual(endpoint, http_client._make_connection_url(""))

View File

@@ -14,7 +14,7 @@
# License for the specific language governing permissions and limitations
# under the License.
from unittest import mock
import mock
from watcherclient.common.apiclient import exceptions as exc
from watcherclient.common import utils

View File

@@ -14,12 +14,12 @@
# under the License.
import copy
import io
import os
from unittest import mock
import fixtures
import mock
from oslo_utils import strutils
import six
import testtools
@@ -51,7 +51,7 @@ class FakeAPI(object):
def raw_request(self, *args, **kwargs):
response = self._request(*args, **kwargs)
body_iter = iter(io.StringIO(response[1]))
body_iter = iter(six.StringIO(response[1]))
return FakeResponse(response[0]), body_iter
def json_request(self, *args, **kwargs):

View File

@@ -14,8 +14,8 @@
# limitations under the License.
import shlex
from unittest import mock
import mock
from osc_lib import utils as oscutils
from oslo_serialization import jsonutils
@@ -25,7 +25,7 @@ from watcherclient.tests.unit import utils
class CommandTestCase(utils.BaseTestCase):
def setUp(self, os_infra_optim_api_version='1.0'):
def setUp(self):
super(CommandTestCase, self).setUp()
self.fake_env = {
@@ -38,7 +38,7 @@ class CommandTestCase(utils.BaseTestCase):
'os_username': 'test',
'os_password': 'test',
'timeout': 600,
'os_infra_optim_api_version': os_infra_optim_api_version}
'os_watcher_api_version': '1'}
self.m_env = mock.Mock(
name='m_env',
side_effect=lambda x, *args, **kwargs: self.fake_env.get(
@@ -53,7 +53,7 @@ class CommandTestCase(utils.BaseTestCase):
self.addCleanup(self.p_construct_http_client.stop)
def run_cmd(self, cmd, formatting='json'):
if formatting and formatting != 'table':
if formatting:
formatter_arg = " -f %s" % formatting
formatter = jsonutils.loads
else:

View File

@@ -92,10 +92,6 @@ fake_responses = {
{},
None,
),
'PATCH': (
{},
ACTION1,
),
},
'/v1/actions/detail?action_plan_uuid=%s' % ACTION1['action_plan']:
{
@@ -268,12 +264,3 @@ class ActionManagerTest(testtools.TestCase):
self.assertEqual(ACTION1['uuid'], action.uuid)
self.assertEqual(ACTION1['action_plan'], action.action_plan)
self.assertEqual(ACTION1['next'], action.next)
def test_actions_update(self):
patch = [{'op': 'replace', 'path': '/state', 'value': 'SKIPPED'}]
action = self.mgr.update(ACTION1['uuid'], patch)
expect = [
('PATCH', '/v1/actions/%s' % ACTION1['uuid'], {}, patch),
]
self.assertEqual(expect, self.api.calls)
self.assertEqual(ACTION1['uuid'], action.uuid)

View File

@@ -14,13 +14,10 @@
# under the License.
import copy
from unittest import mock
import testtools
from testtools import matchers
from oslo_utils.uuidutils import generate_uuid
from watcherclient.common.apiclient.exceptions import HTTPClientError
from watcherclient.tests.unit import utils
import watcherclient.v1.action_plan
@@ -41,19 +38,9 @@ ACTION_PLAN2 = {
UPDATED_ACTION_PLAN = copy.deepcopy(ACTION_PLAN1)
NEW_STATE = 'PENDING'
UPDATED_ACTION_PLAN['state'] = NEW_STATE
START_ACTION_PLAN = copy.deepcopy(ACTION_PLAN1)
START_ACTION_PLAN['state'] = NEW_STATE
ONGOING_ACTION_PLAN = copy.deepcopy(ACTION_PLAN1)
ONGOING_ACTION_PLAN['state'] = 'ONGOING'
CANCELLING_ACTION_PLAN = copy.deepcopy(ACTION_PLAN1)
CANCELLING_ACTION_PLAN['state'] = 'CANCELLING'
CANCELD_ACTION_PLAN = copy.deepcopy(ACTION_PLAN2)
CANCELD_ACTION_PLAN['state'] = 'CANCELLED'
fake_responses = {
'/v1/action_plans':
{
@@ -142,31 +129,6 @@ fake_responses_sorting = {
},
}
fake_responses_cancel = {
'/v1/action_plans/%s' % ACTION_PLAN1['uuid']:
{
'GET': (
{},
[ONGOING_ACTION_PLAN],
),
'PATCH': (
{},
CANCELLING_ACTION_PLAN,
),
},
'/v1/action_plans/%s' % ACTION_PLAN2['uuid']:
{
'GET': (
{},
[ACTION_PLAN2],
),
'PATCH': (
{},
CANCELD_ACTION_PLAN,
),
},
}
class ActionPlanManagerTest(testtools.TestCase):
@@ -253,54 +215,6 @@ class ActionPlanManagerTest(testtools.TestCase):
self.assertEqual(expect, self.api.calls)
self.assertEqual(ACTION_PLAN1['uuid'], action_plan.uuid)
def test_action_plans_get_index_error(self):
# verify this method will return None when meet indexError
fake_uuid = generate_uuid()
self.api.json_request = mock.Mock(return_value=('404', []))
self.assertIsNone(self.mgr.get(fake_uuid))
def test_action_plans_delete(self):
# verity that action plan was successfully deleted
self.api.raw_request = mock.Mock(return_value=('204', []))
self.assertIsNone(self.mgr.delete(ACTION_PLAN1['uuid']))
# verity that delete a wrong action plan will raise Exception
fake_uuid = generate_uuid()
self.api.raw_request = mock.Mock(
side_effect=HTTPClientError('404 Not Found'))
self.assertRaises(HTTPClientError, self.mgr.delete, fake_uuid)
def test_action_plans_cancel(self):
# verity that the status of action plan can be converted from
# 'ONGOING' to 'CANCELLING'
self.api = utils.FakeAPI(fake_responses_cancel)
self.mgr = watcherclient.v1.action_plan.ActionPlanManager(self.api)
patch = {'op': 'replace',
'value': 'CANCELLING',
'path': '/state'}
action_plan = self.mgr.cancel(action_plan_id=ACTION_PLAN1['uuid'])
expect = [
('GET', '/v1/action_plans/%s' % ACTION_PLAN1['uuid'], {}, None),
('PATCH', '/v1/action_plans/%s' % ACTION_PLAN1['uuid'], {},
[patch])
]
self.assertEqual(expect, self.api.calls)
self.assertEqual('CANCELLING', action_plan.state)
# verity that the status of action plan can be converted from
# 'RECOMMENDED' to 'CANCELLED'
patch['value'] = 'CANCELLED'
self.api.calls = []
action_plan = self.mgr.cancel(action_plan_id=ACTION_PLAN2['uuid'])
expect = [
('GET', '/v1/action_plans/%s' % ACTION_PLAN2['uuid'], {}, None),
('PATCH', '/v1/action_plans/%s' % ACTION_PLAN2['uuid'], {},
[patch])
]
self.assertEqual(expect, self.api.calls)
self.assertEqual('CANCELLED', action_plan.state)
def test_action_plan_update(self):
patch = {'op': 'replace',
'value': NEW_STATE,

View File

@@ -14,10 +14,8 @@
# limitations under the License.
import datetime
import io
from unittest import mock
from oslo_utils.uuidutils import generate_uuid
import mock
import six
from watcherclient import exceptions
from watcherclient import shell
@@ -79,9 +77,8 @@ class ActionPlanShellTest(base.CommandTestCase):
FIELD_LABELS = resource_fields.ACTION_PLAN_FIELD_LABELS
GLOBAL_EFFICACY_FIELDS = resource_fields.GLOBAL_EFFICACY_FIELDS
def setUp(self, os_infra_optim_api_version='1.0'):
super(ActionPlanShellTest, self).setUp(
os_infra_optim_api_version=os_infra_optim_api_version)
def setUp(self):
super(self.__class__, self).setUp()
p_audit_manager = mock.patch.object(resource, 'AuditManager')
p_audit_template_manager = mock.patch.object(
@@ -105,7 +102,7 @@ class ActionPlanShellTest(base.CommandTestCase):
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 = io.StringIO()
self.stdout = six.StringIO()
self.cmd = shell.WatcherShell(stdout=self.stdout)
def test_do_action_plan_list(self):
@@ -129,17 +126,6 @@ class ActionPlanShellTest(base.CommandTestCase):
self.assertEqual(action_plan2.global_efficacy,
results[1]['Global efficacy'])
def test_do_action_plan_list_by_table(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', 'table')
self.assertEqual(0, exit_code)
self.assertIn(ACTION_PLAN_1['uuid'], results)
self.assertIn(ACTION_PLAN_2['uuid'], results)
self.m_action_plan_mgr.list.assert_called_once_with(detail=False)
def test_do_action_plan_list_detail(self):
@@ -209,32 +195,6 @@ class ActionPlanShellTest(base.CommandTestCase):
self.assertEqual(1, exit_code)
self.assertEqual('', result)
def test_do_action_plan_show_by_random_uuid(self):
# verify that show a wrong actionplan will raise Exception
self.m_action_plan_mgr.get.side_effect = exceptions.HTTPNotFound
fake_uuid = generate_uuid()
exit_code, result = self.run_cmd(
'actionplan show {}'.format(fake_uuid), formatting=None)
self.assertEqual(1, exit_code)
self.assertEqual('', result)
self.m_action_plan_mgr.get.assert_called_once_with(fake_uuid)
def test_do_action_plan_show_uuid_by_table(self):
# verify that show an actionplan can be in a 'table' format
action_plan = resource.ActionPlan(mock.Mock(), ACTION_PLAN_1)
self.m_action_plan_mgr.get.return_value = action_plan
exit_code, result = self.run_cmd(
'actionplan show d9d9978e-6db5-4a05-8eab-1531795d7004',
formatting='table')
self.assertEqual(0, exit_code)
self.assertIn(ACTION_PLAN_1['uuid'], result)
self.m_action_plan_mgr.get.assert_called_once_with(
'd9d9978e-6db5-4a05-8eab-1531795d7004')
def test_do_action_plan_delete(self):
self.m_action_plan_mgr.delete.return_value = ''
@@ -337,14 +297,3 @@ class ActionPlanShellTest(base.CommandTestCase):
self.assertEqual(1, exit_code)
self.assertEqual('', result)
class ActionPlanShellTest15(ActionPlanShellTest):
def setUp(self):
super(ActionPlanShellTest15, self).setUp(
os_infra_optim_api_version='1.5')
v15 = dict(status_message=None)
for action_plan in (ACTION_PLAN_1, ACTION_PLAN_2):
action_plan.update(v15)
self.FIELDS.extend(['status_message'])
self.FIELD_LABELS.extend(['Status Message'])

View File

@@ -14,9 +14,8 @@
# under the License.
import datetime
import io
import unittest
from unittest import mock
import mock
import six
from watcherclient import exceptions
from watcherclient import shell
@@ -79,9 +78,8 @@ class ActionShellTest(base.CommandTestCase):
FIELDS = resource_fields.ACTION_FIELDS
FIELD_LABELS = resource_fields.ACTION_FIELD_LABELS
def setUp(self, os_infra_optim_api_version='1.0'):
super(ActionShellTest, self).setUp(
os_infra_optim_api_version=os_infra_optim_api_version)
def setUp(self):
super(self.__class__, self).setUp()
p_action_manager = mock.patch.object(resource, 'ActionManager')
p_action_plan_manager = mock.patch.object(
@@ -97,7 +95,7 @@ class ActionShellTest(base.CommandTestCase):
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 = io.StringIO()
self.stdout = six.StringIO()
self.cmd = shell.WatcherShell(stdout=self.stdout)
def test_do_action_list(self):
@@ -178,106 +176,3 @@ class ActionShellTest(base.CommandTestCase):
self.assertEqual(1, exit_code)
self.assertEqual('', result)
def test_do_action_update_unsupported_version(self):
exit_code, result = self.run_cmd(
'action update --state SKIPPED '
'770ef053-ecb3-48b0-85b5-d55a2dbc6588',
formatting=None)
self.assertEqual(1, exit_code)
self.assertEqual('', result)
class ActionShellTest15(ActionShellTest):
def setUp(self):
super(ActionShellTest15, self).setUp(os_infra_optim_api_version='1.5')
v15 = dict(status_message=None)
for action in (ACTION_1, ACTION_2, ACTION_3):
action.update(v15)
self.FIELDS.extend(['status_message'])
self.FIELD_LABELS.extend(['Status Message'])
def test_do_action_update_with_state_only(self):
action = resource.Action(mock.Mock(), ACTION_1)
self.m_action_mgr.update.return_value = action
exit_code, result = self.run_cmd(
'action update --state SKIPPED '
'770ef053-ecb3-48b0-85b5-d55a2dbc6588')
self.assertEqual(0, exit_code)
self.assertEqual(
self.resource_as_dict(action, self.FIELDS, self.FIELD_LABELS),
result)
expected_patch = [
{'op': 'replace', 'path': '/state', 'value': 'SKIPPED'}
]
self.m_action_mgr.update.assert_called_once_with(
'770ef053-ecb3-48b0-85b5-d55a2dbc6588', expected_patch)
def test_do_action_update_with_state_and_reason(self):
action = resource.Action(mock.Mock(), ACTION_1)
self.m_action_mgr.update.return_value = action
exit_code, result = self.run_cmd(
'action update --state SKIPPED --reason "Manual skip" '
'770ef053-ecb3-48b0-85b5-d55a2dbc6588')
self.assertEqual(0, exit_code)
self.assertEqual(
self.resource_as_dict(action, self.FIELDS, self.FIELD_LABELS),
result)
expected_patch = [
{'op': 'replace', 'path': '/state', 'value': 'SKIPPED'},
{'op': 'replace', 'path': '/status_message',
'value': 'Manual skip'}
]
self.m_action_mgr.update.assert_called_once_with(
'770ef053-ecb3-48b0-85b5-d55a2dbc6588', expected_patch)
def test_do_action_update_with_reason_only(self):
action = resource.Action(mock.Mock(), ACTION_1)
self.m_action_mgr.update.return_value = action
exit_code, result = self.run_cmd(
'action update --reason "Manual skip" '
'770ef053-ecb3-48b0-85b5-d55a2dbc6588')
self.assertEqual(0, exit_code)
self.assertEqual(
self.resource_as_dict(action, self.FIELDS, self.FIELD_LABELS),
result)
expected_patch = [
{'op': 'replace', 'path': '/status_message',
'value': 'Manual skip'}
]
self.m_action_mgr.update.assert_called_once_with(
'770ef053-ecb3-48b0-85b5-d55a2dbc6588', expected_patch)
def test_do_action_update_no_fields_to_update(self):
exit_code, result = self.run_cmd(
'action update 770ef053-ecb3-48b0-85b5-d55a2dbc6588',
formatting=None)
self.assertEqual(1, exit_code)
self.assertEqual('', result)
def test_do_action_update_action_not_found(self):
self.m_action_mgr.update.side_effect = exceptions.HTTPNotFound
exit_code, result = self.run_cmd(
'action update --state SKIPPED not_found_uuid',
formatting=None)
self.assertEqual(1, exit_code)
self.assertEqual('', result)
@unittest.skip("Action update is supported in API version 1.5")
def test_do_action_update_unsupported_version(self):
pass

427
watcherclient/tests/unit/v1/test_audit_shell.py Normal file → Executable file
View File

@@ -14,12 +14,13 @@
# under the License.
import datetime
import io
from unittest import mock
import mock
import six
from watcherclient import shell
from watcherclient.tests.unit.v1 import base
from watcherclient import v1 as resource
from watcherclient.v1 import resource_fields
AUDIT_TEMPLATE_1 = {
'uuid': 'f8e47706-efcf-49a4-a5c4-af604eb492f2',
@@ -51,86 +52,76 @@ STRATEGY_1 = {
'deleted_at': None,
}
AUDIT_1 = {
'uuid': '5869da81-4876-4687-a1ed-12cd64cf53d9',
'audit_type': 'ONESHOT',
'state': 'PENDING',
'audit_template_uuid': 'f8e47706-efcf-49a4-a5c4-af604eb492f2',
'audit_template_name': 'at1',
'goal_name': 'SERVER_CONSOLIDATION',
'strategy_name': 'basic',
'created_at': datetime.datetime.now().isoformat(),
'updated_at': None,
'deleted_at': None,
'parameters': None,
'interval': None,
'scope': '',
'auto_trigger': False,
'next_run_time': None,
'name': 'my_audit1',
'hostname': '',
}
AUDIT_2 = {
'uuid': 'a5199d0e-0702-4613-9234-5ae2af8dafea',
'audit_type': 'ONESHOT',
'state': 'PENDING',
'audit_template_uuid': 'f8e47706-efcf-49a4-a5c4-af604eb492f2',
'audit_template_name': 'at1',
'goal_name': 'fc087747-61be-4aad-8126-b701731ae836',
'strategy_name': 'auto',
'created_at': datetime.datetime.now().isoformat(),
'updated_at': None,
'deleted_at': None,
'parameters': None,
'interval': None,
'scope': '',
'auto_trigger': False,
'next_run_time': None,
'name': 'my_audit2',
'hostname': '',
}
AUDIT_3 = {
'uuid': '43199d0e-0712-1213-9674-5ae2af8dhgte',
'audit_type': 'ONESHOT',
'state': 'PENDING',
'audit_template_uuid': 'f8e47706-efcf-49a4-a5c4-af604eb492f2',
'audit_template_name': 'at1',
'goal_name': None,
'strategy_name': 'auto',
'created_at': datetime.datetime.now().isoformat(),
'updated_at': None,
'deleted_at': None,
'parameters': None,
'interval': 3600,
'scope': '',
'auto_trigger': True,
'next_run_time': None,
'name': 'my_audit3',
'hostname': '',
}
class AuditShellTest(base.CommandTestCase):
AUDIT_1 = {
'uuid': '5869da81-4876-4687-a1ed-12cd64cf53d9',
'audit_type': 'ONESHOT',
'state': 'PENDING',
'audit_template_uuid': 'f8e47706-efcf-49a4-a5c4-af604eb492f2',
'audit_template_name': 'at1',
'goal_name': 'SERVER_CONSOLIDATION',
'strategy_name': 'basic',
'created_at': datetime.datetime.now().isoformat(),
'updated_at': None,
'deleted_at': None,
'parameters': None,
'interval': None,
'scope': '',
'auto_trigger': False,
'next_run_time': None,
'name': 'my_audit1',
'hostname': '',
}
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
AUDIT_2 = {
'uuid': 'a5199d0e-0702-4613-9234-5ae2af8dafea',
'audit_type': 'ONESHOT',
'state': 'PENDING',
'audit_template_uuid': 'f8e47706-efcf-49a4-a5c4-af604eb492f2',
'audit_template_name': 'at1',
'goal_name': 'fc087747-61be-4aad-8126-b701731ae836',
'strategy_name': 'auto',
'created_at': datetime.datetime.now().isoformat(),
'updated_at': None,
'deleted_at': None,
'parameters': None,
'interval': None,
'scope': '',
'auto_trigger': False,
'next_run_time': None,
'name': 'my_audit2',
'hostname': '',
}
AUDIT_3 = {
'uuid': '43199d0e-0712-1213-9674-5ae2af8dhgte',
'audit_type': 'ONESHOT',
'state': 'PENDING',
'audit_template_uuid': 'f8e47706-efcf-49a4-a5c4-af604eb492f2',
'audit_template_name': 'at1',
'goal_name': None,
'strategy_name': 'auto',
'created_at': datetime.datetime.now().isoformat(),
'updated_at': None,
'deleted_at': None,
'parameters': None,
'interval': 3600,
'scope': '',
'auto_trigger': True,
'next_run_time': None,
'name': 'my_audit3',
'hostname': '',
}
SHORT_LIST_FIELDS = ['uuid', 'name', 'audit_type',
'state', 'goal_name', 'strategy_name',
'auto_trigger']
SHORT_LIST_FIELD_LABELS = ['UUID', 'Name', 'Audit Type', 'State', 'Goal',
'Strategy', 'Auto Trigger']
FIELDS = ['uuid', 'name', 'created_at', 'updated_at', 'deleted_at',
'state', 'audit_type', 'parameters', 'interval', 'goal_name',
'strategy_name', 'scope', 'auto_trigger', 'next_run_time',
'hostname']
FIELD_LABELS = ['UUID', 'Name', 'Created At', 'Updated At', 'Deleted At',
'State', 'Audit Type', 'Parameters', 'Interval', 'Goal',
'Strategy', 'Audit Scope', 'Auto Trigger',
'Next Run Time', 'Hostname']
def setUp(self, os_infra_optim_api_version='1.0'):
super(AuditShellTest, self).setUp(
os_infra_optim_api_version=os_infra_optim_api_version)
def setUp(self):
super(self.__class__, self).setUp()
# goal mock
p_goal_manager = mock.patch.object(resource, 'GoalManager')
@@ -163,12 +154,12 @@ class AuditShellTest(base.CommandTestCase):
self.m_audit_template_mgr_cls.return_value = self.m_audit_template_mgr
# stdout mock
self.stdout = io.StringIO()
self.stdout = six.StringIO()
self.cmd = shell.WatcherShell(stdout=self.stdout)
def test_do_audit_list(self):
audit1 = resource.Audit(mock.Mock(), self.AUDIT_1)
audit2 = resource.Audit(mock.Mock(), self.AUDIT_2)
audit1 = resource.Audit(mock.Mock(), AUDIT_1)
audit2 = resource.Audit(mock.Mock(), AUDIT_2)
self.m_audit_mgr.list.return_value = [
audit1, audit2]
@@ -185,7 +176,7 @@ class AuditShellTest(base.CommandTestCase):
self.m_audit_mgr.list.assert_called_once_with(detail=False)
def test_do_audit_list_marker(self):
audit2 = resource.Audit(mock.Mock(), self.AUDIT_2)
audit2 = resource.Audit(mock.Mock(), AUDIT_2)
self.m_audit_mgr.list.return_value = [audit2]
exit_code, results = self.run_cmd(
@@ -202,8 +193,8 @@ class AuditShellTest(base.CommandTestCase):
marker='5869da81-4876-4687-a1ed-12cd64cf53d9')
def test_do_audit_list_detail(self):
audit1 = resource.Audit(mock.Mock(), self.AUDIT_1)
audit2 = resource.Audit(mock.Mock(), self.AUDIT_2)
audit1 = resource.Audit(mock.Mock(), AUDIT_1)
audit2 = resource.Audit(mock.Mock(), AUDIT_2)
self.m_audit_mgr.list.return_value = [
audit1, audit2]
@@ -220,7 +211,7 @@ class AuditShellTest(base.CommandTestCase):
self.m_audit_mgr.list.assert_called_once_with(detail=True)
def test_do_audit_show_by_uuid(self):
audit = resource.Audit(mock.Mock(), self.AUDIT_1)
audit = resource.Audit(mock.Mock(), AUDIT_1)
self.m_audit_mgr.get.return_value = audit
exit_code, result = self.run_cmd(
@@ -234,7 +225,7 @@ class AuditShellTest(base.CommandTestCase):
'5869da81-4876-4687-a1ed-12cd64cf53d9')
def test_do_audit_show_by_name(self):
audit = resource.Audit(mock.Mock(), self.AUDIT_1)
audit = resource.Audit(mock.Mock(), AUDIT_1)
self.m_audit_mgr.get.return_value = audit
exit_code, result = self.run_cmd(
@@ -287,7 +278,7 @@ class AuditShellTest(base.CommandTestCase):
'5b157edd-5a7e-4aaa-b511-f7b33ec86e9f')
def test_do_audit_update(self):
audit = resource.Audit(mock.Mock(), self.AUDIT_1)
audit = resource.Audit(mock.Mock(), AUDIT_1)
self.m_audit_mgr.update.return_value = audit
exit_code, result = self.run_cmd(
@@ -303,7 +294,7 @@ class AuditShellTest(base.CommandTestCase):
[{'op': 'replace', 'path': '/state', 'value': 'PENDING'}])
def test_do_audit_update_by_name(self):
audit = resource.Audit(mock.Mock(), self.AUDIT_1)
audit = resource.Audit(mock.Mock(), AUDIT_1)
self.m_audit_mgr.update.return_value = audit
exit_code, result = self.run_cmd(
@@ -318,7 +309,7 @@ class AuditShellTest(base.CommandTestCase):
[{'op': 'replace', 'path': '/state', 'value': 'PENDING'}])
def test_do_audit_create_with_audit_template_uuid(self):
audit = resource.Audit(mock.Mock(), self.AUDIT_3)
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
@@ -337,7 +328,7 @@ class AuditShellTest(base.CommandTestCase):
)
def test_do_audit_create_with_audit_template_name(self):
audit = resource.Audit(mock.Mock(), self.AUDIT_3)
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
@@ -355,7 +346,7 @@ class AuditShellTest(base.CommandTestCase):
)
def test_do_audit_create_with_goal(self):
audit = resource.Audit(mock.Mock(), self.AUDIT_1)
audit = resource.Audit(mock.Mock(), AUDIT_1)
self.m_audit_mgr.create.return_value = audit
exit_code, result = self.run_cmd(
@@ -372,7 +363,7 @@ class AuditShellTest(base.CommandTestCase):
)
def test_do_audit_create_with_goal_and_strategy(self):
audit = resource.Audit(mock.Mock(), self.AUDIT_1)
audit = resource.Audit(mock.Mock(), AUDIT_1)
self.m_audit_mgr.create.return_value = audit
exit_code, result = self.run_cmd(
@@ -391,7 +382,7 @@ class AuditShellTest(base.CommandTestCase):
)
def test_do_audit_create_with_type(self):
audit = resource.Audit(mock.Mock(), self.AUDIT_1)
audit = resource.Audit(mock.Mock(), AUDIT_1)
self.m_audit_mgr.create.return_value = audit
exit_code, result = self.run_cmd(
@@ -408,7 +399,7 @@ class AuditShellTest(base.CommandTestCase):
)
def test_do_audit_create_with_parameter(self):
audit = resource.Audit(mock.Mock(), self.AUDIT_1)
audit = resource.Audit(mock.Mock(), AUDIT_1)
self.m_audit_mgr.create.return_value = audit
exit_code, result = self.run_cmd(
@@ -426,25 +417,8 @@ class AuditShellTest(base.CommandTestCase):
parameters={'para1': 10, 'para2': 20}
)
def test_do_audit_create_with_type_event(self):
audit = resource.Audit(mock.Mock(), self.AUDIT_1)
self.m_audit_mgr.create.return_value = audit
exit_code, result = self.run_cmd(
'audit create -g fc087747-61be-4aad-8126-b701731ae836 -t EVENT')
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(
goal='fc087747-61be-4aad-8126-b701731ae836',
auto_trigger=False,
audit_type='EVENT'
)
def test_do_audit_create_with_type_continuous(self):
audit = resource.Audit(mock.Mock(), self.AUDIT_1)
audit = resource.Audit(mock.Mock(), AUDIT_1)
self.m_audit_mgr.create.return_value = audit
exit_code, result = self.run_cmd(
@@ -463,7 +437,7 @@ class AuditShellTest(base.CommandTestCase):
)
def test_do_audit_create_with_name(self):
audit = resource.Audit(mock.Mock(), self.AUDIT_1)
audit = resource.Audit(mock.Mock(), AUDIT_1)
self.m_audit_mgr.create.return_value = audit
exit_code, result = self.run_cmd(
@@ -481,230 +455,3 @@ class AuditShellTest(base.CommandTestCase):
interval='3600',
name='my_audit'
)
class AuditShellTestv11(AuditShellTest):
def setUp(self):
super(AuditShellTestv11, self).setUp(os_infra_optim_api_version='1.1')
v11 = dict(start_time=None, end_time=None)
for audit in (self.AUDIT_1, self.AUDIT_2, self.AUDIT_3):
audit.update(v11)
self.FIELDS.extend(['start_time', 'end_time'])
self.FIELD_LABELS.extend(['Start Time', 'End Time'])
class AuditShellTestv12(AuditShellTest):
def setUp(self, os_infra_optim_api_version='1.2'):
super(AuditShellTestv12, self).setUp(
os_infra_optim_api_version=os_infra_optim_api_version)
v11 = dict(start_time=None, end_time=None)
v12 = dict(force=False)
for audit in (self.AUDIT_1, self.AUDIT_2, self.AUDIT_3):
audit.update(v11)
audit.update(v12)
self.FIELDS.extend(['start_time', 'end_time', 'force'])
self.FIELD_LABELS.extend(['Start Time', 'End Time', 'Force'])
def test_do_audit_create_with_force(self):
audit = resource.Audit(mock.Mock(), self.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 f8e47706-efcf-49a4-a5c4-af604eb492f2 --force')
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',
auto_trigger=False,
force=True
)
def test_do_audit_create_with_audit_template_uuid(self):
audit = resource.Audit(mock.Mock(), self.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 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',
auto_trigger=False,
force=False
)
def test_do_audit_create_with_audit_template_name(self):
audit = resource.Audit(mock.Mock(), self.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')
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',
auto_trigger=False,
audit_type='ONESHOT',
force=False
)
def test_do_audit_create_with_goal(self):
audit = resource.Audit(mock.Mock(), self.AUDIT_1)
self.m_audit_mgr.create.return_value = audit
exit_code, result = self.run_cmd(
'audit create -g fc087747-61be-4aad-8126-b701731ae836')
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(
goal='fc087747-61be-4aad-8126-b701731ae836',
auto_trigger=False,
audit_type='ONESHOT',
force=False
)
def test_do_audit_create_with_goal_and_strategy(self):
audit = resource.Audit(mock.Mock(), self.AUDIT_1)
self.m_audit_mgr.create.return_value = audit
exit_code, result = self.run_cmd(
'audit create -g fc087747-61be-4aad-8126-b701731ae836 -s '
'2cf86250-d309-4b81-818e-1537f3dba6e5')
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(
goal='fc087747-61be-4aad-8126-b701731ae836',
strategy='2cf86250-d309-4b81-818e-1537f3dba6e5',
auto_trigger=False,
audit_type='ONESHOT',
force=False
)
def test_do_audit_create_with_type(self):
audit = resource.Audit(mock.Mock(), self.AUDIT_1)
self.m_audit_mgr.create.return_value = audit
exit_code, result = self.run_cmd(
'audit create -g fc087747-61be-4aad-8126-b701731ae836 -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(
goal='fc087747-61be-4aad-8126-b701731ae836',
auto_trigger=False,
audit_type='ONESHOT',
force=False
)
def test_do_audit_create_with_parameter(self):
audit = resource.Audit(mock.Mock(), self.AUDIT_1)
self.m_audit_mgr.create.return_value = audit
exit_code, result = self.run_cmd(
'audit create -g fc087747-61be-4aad-8126-b701731ae836 -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(
goal='fc087747-61be-4aad-8126-b701731ae836',
audit_type='ONESHOT',
auto_trigger=False,
parameters={'para1': 10, 'para2': 20},
force=False
)
def test_do_audit_create_with_type_continuous(self):
audit = resource.Audit(mock.Mock(), self.AUDIT_1)
self.m_audit_mgr.create.return_value = audit
exit_code, result = self.run_cmd(
'audit create -g fc087747-61be-4aad-8126-b701731ae836 '
'-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(
goal='fc087747-61be-4aad-8126-b701731ae836',
audit_type='CONTINUOUS',
auto_trigger=False,
interval='3600',
force=False
)
def test_do_audit_create_with_type_event(self):
audit = resource.Audit(mock.Mock(), self.AUDIT_1)
self.m_audit_mgr.create.return_value = audit
exit_code, result = self.run_cmd(
'audit create -g fc087747-61be-4aad-8126-b701731ae836 -t EVENT')
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(
goal='fc087747-61be-4aad-8126-b701731ae836',
auto_trigger=False,
audit_type='EVENT',
force=False
)
def test_do_audit_create_with_name(self):
audit = resource.Audit(mock.Mock(), self.AUDIT_1)
self.m_audit_mgr.create.return_value = audit
exit_code, result = self.run_cmd(
'audit create -g fc087747-61be-4aad-8126-b701731ae836 '
'-t CONTINUOUS -i 3600 --name my_audit')
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(
goal='fc087747-61be-4aad-8126-b701731ae836',
audit_type='CONTINUOUS',
auto_trigger=False,
interval='3600',
name='my_audit',
force=False
)
class AuditShellTestv15(AuditShellTestv12):
def setUp(self):
super(AuditShellTestv15, self).setUp(os_infra_optim_api_version='1.5')
v15 = dict(status_message=None)
for audit in (self.AUDIT_1, self.AUDIT_2, self.AUDIT_3):
audit.update(v15)
self.FIELDS.extend(['status_message'])
self.FIELD_LABELS.extend(['Status Message'])

View File

@@ -14,8 +14,8 @@
# under the License.
import copy
from urllib import parse as urlparse
from six.moves.urllib import parse as urlparse
from testtools import matchers
from watcherclient.tests.unit import utils

View File

@@ -14,8 +14,8 @@
# under the License.
import datetime
import io
from unittest import mock
import mock
import six
from watcherclient import shell
from watcherclient.tests.unit.v1 import base
@@ -107,7 +107,7 @@ class AuditTemplateShellTest(base.CommandTestCase):
self.m_audit_template_mgr_cls.return_value = self.m_audit_template_mgr
# stdout mock
self.stdout = io.StringIO()
self.stdout = six.StringIO()
self.cmd = shell.WatcherShell(stdout=self.stdout)
def test_do_audit_template_list(self):

View File

@@ -1,75 +0,0 @@
# Copyright 2019 ZTE corporation.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import testtools
from watcherclient.tests.unit import utils
import watcherclient.v1.data_model
DATA_MODEL = {
'context': [{
"server_uuid": "1bf91464-9b41-428d-a11e-af691e5563bb",
"server_name": "fake-name",
"server_state": "active",
"node_uuid": "253e5dd0-9384-41ab-af13-4f2c2ce26112",
"node_hostname": "localhost.localdomain",
}]
}
AUDIT = "81332bfc-36f8-444d-99e2-b7285d602528"
fake_responses = {
'/v1/data_model/?data_model_type=compute':
{
'GET': (
{},
DATA_MODEL,
),
},
'/v1/data_model/?audit_uuid=%s&data_model_type=compute' % AUDIT:
{
'GET': (
{},
DATA_MODEL,
),
},
}
class DataModelManagerTest(testtools.TestCase):
def setUp(self):
super(DataModelManagerTest, self).setUp()
self.api = utils.FakeAPI(fake_responses)
self.mgr = watcherclient.v1.data_model.DataModelManager(self.api)
def test_data_model_list(self):
data_model = self.mgr.list()
expect = [
('GET', '/v1/data_model/?data_model_type=compute', {}, None),
]
self.assertEqual(expect, self.api.calls)
self.assertEqual(1, len(data_model.context))
def test_data_model_list_audit(self):
data_model = self.mgr.list(
audit='%s' % AUDIT)
expect = [
('GET', '/v1/data_model/?'
'audit_uuid=81332bfc-36f8-444d-99e2-b7285d602528'
'&data_model_type=compute',
{}, None),
]
self.assertEqual(expect, self.api.calls)
self.assertEqual(1, len(data_model.context))

View File

@@ -1,130 +0,0 @@
# Copyright 2019 ZTE 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 io
from unittest import mock
from watcherclient import shell
from watcherclient.tests.unit.v1 import base
from watcherclient import v1 as resource
from watcherclient.v1 import resource_fields
DATA_MODEL = {
'context': [{
"server_uuid": "1bf91464-9b41-428d-a11e-af691e5563bb",
"server_name": "fake-name",
"server_state": "active",
"server_vcpus": "1",
"server_memory": "512",
"server_disk": "1",
"node_uuid": "253e5dd0-9384-41ab-af13-4f2c2ce26112",
"node_hostname": "localhost.localdomain",
"node_vcpus": "4",
"node_vcpu_ratio": "16.0",
"node_memory": "16383",
"node_memory_ratio": "1.5",
"node_disk": "37",
"node_disk_ratio": "1.0",
"node_state": "up",
}]
}
LIST_RESULT = [{
"Server UUID": "1bf91464-9b41-428d-a11e-af691e5563bb",
"Server Name": "fake-name",
"Server Vcpus": "1",
"Server Memory": "512",
"Server Disk": "1",
"Server State": "active",
"Node UUID": "253e5dd0-9384-41ab-af13-4f2c2ce26112",
"Node Host Name": "localhost.localdomain",
"Node Vcpus": "4",
"Node Vcpu Ratio": "16.0",
"Node Memory": "16383",
"Node Memory Ratio": "1.5",
"Node Disk": "37",
"Node Disk Ratio": "1.0",
"Node State": "up",
}]
SHORT_LIST_RESULT = [{
"Server UUID": "1bf91464-9b41-428d-a11e-af691e5563bb",
"Server Name": "fake-name",
"Server State": "active",
"Node UUID": "253e5dd0-9384-41ab-af13-4f2c2ce26112",
"Node Host Name": "localhost.localdomain",
}]
class DataModelShellTest(base.CommandTestCase):
SHORT_LIST_FIELDS = resource_fields.COMPUTE_MODEL_SHORT_LIST_FIELDS
SHORT_LIST_FIELD_LABELS = (
resource_fields.COMPUTE_MODEL_SHORT_LIST_FIELD_LABELS)
FIELDS = resource_fields.COMPUTE_MODEL_LIST_FIELDS
FIELD_LABELS = resource_fields.COMPUTE_MODEL_LIST_FIELD_LABELS
def setUp(self):
super(self.__class__, self).setUp()
p_data_model_manager = mock.patch.object(
resource, 'DataModelManager')
self.m_data_model_mgr_cls = p_data_model_manager.start()
self.addCleanup(p_data_model_manager.stop)
self.m_data_model_mgr = mock.Mock()
self.m_data_model_mgr_cls.return_value = self.m_data_model_mgr
self.stdout = io.StringIO()
self.cmd = shell.WatcherShell(stdout=self.stdout)
def test_do_data_model_list(self):
data_model = resource.DataModel(mock.Mock(), DATA_MODEL)
self.m_data_model_mgr.list.return_value = data_model
exit_code, results = self.run_cmd('datamodel list')
self.assertEqual(0, exit_code)
expect_values = sorted(SHORT_LIST_RESULT[0].values())
result_values = sorted(results[0].values())
self.assertEqual(expect_values, result_values)
def test_do_data_model_list_detail(self):
data_model = resource.DataModel(mock.Mock(), DATA_MODEL)
self.m_data_model_mgr.list.return_value = data_model
exit_code, results = self.run_cmd('datamodel list --detail')
self.assertEqual(0, exit_code)
expect_values = sorted(LIST_RESULT[0].values())
result_values = sorted(results[0].values())
self.assertEqual(expect_values, result_values)
def test_do_data_model_list_filter_by_audit(self):
data_model = resource.DataModel(mock.Mock(), DATA_MODEL)
self.m_data_model_mgr.list.return_value = data_model
exit_code, results = self.run_cmd(
'datamodel list --audit '
'770ef053-ecb3-48b0-85b5-d55a2dbc6588')
self.assertEqual(0, exit_code)
expect_values = sorted(SHORT_LIST_RESULT[0].values())
result_values = sorted(results[0].values())
self.assertEqual(expect_values, result_values)

View File

@@ -98,16 +98,6 @@ fake_responses_sorting = {
},
}
fake_responses_marker = {
'/v1/goals/?marker=fc087747-61be-4aad-8126-b701731ae836':
{
'GET': (
{},
{"goals": [GOAL2]}
),
},
}
class GoalManagerTest(testtools.TestCase):
@@ -142,17 +132,6 @@ class GoalManagerTest(testtools.TestCase):
self.assertEqual(expect, self.api.calls)
self.assertThat(goals, matchers.HasLength(1))
def test_goals_list_marker(self):
self.api = utils.FakeAPI(fake_responses_marker)
self.mgr = watcherclient.v1.goal.GoalManager(self.api)
goals = self.mgr.list(marker=GOAL1['uuid'])
expect = [
('GET', '/v1/goals/?marker=fc087747-61be-4aad-8126-b701731ae836',
{}, None),
]
self.assertEqual(expect, self.api.calls)
self.assertEqual(1, len(goals))
def test_goals_list_pagination_no_limit(self):
self.api = utils.FakeAPI(fake_responses_pagination)
self.mgr = watcherclient.v1.goal.GoalManager(self.api)

View File

@@ -14,8 +14,8 @@
# limitations under the License.
import datetime
import io
from unittest import mock
import mock
import six
from watcherclient import shell
from watcherclient.tests.unit.v1 import base
@@ -72,7 +72,7 @@ class GoalShellTest(base.CommandTestCase):
self.m_goal_mgr = mock.Mock()
self.m_goal_mgr_cls.return_value = self.m_goal_mgr
self.stdout = io.StringIO()
self.stdout = six.StringIO()
self.cmd = shell.WatcherShell(stdout=self.stdout)
def test_do_goal_list(self):
@@ -93,23 +93,6 @@ class GoalShellTest(base.CommandTestCase):
self.m_goal_mgr.list.assert_called_once_with(detail=False)
def test_do_goal_list_marker(self):
goal2 = resource.Goal(mock.Mock(), GOAL_2)
self.m_goal_mgr.list.return_value = [goal2]
exit_code, results = self.run_cmd(
'goal list --marker fc087747-61be-4aad-8126-b701731ae836')
self.assertEqual(0, exit_code)
self.assertEqual(
[self.resource_as_dict(goal2, self.SHORT_LIST_FIELDS,
self.SHORT_LIST_FIELD_LABELS)],
results)
self.m_goal_mgr.list.assert_called_once_with(
detail=False,
marker='fc087747-61be-4aad-8126-b701731ae836')
def test_do_goal_list_detail(self):
goal1 = resource.Goal(mock.Mock(), GOAL_1)
goal2 = resource.Goal(mock.Mock(), GOAL_2)

View File

@@ -14,8 +14,8 @@
# limitations under the License.
import datetime
import io
from unittest import mock
import mock
import six
from watcherclient import shell
from watcherclient.tests.unit.v1 import base
@@ -62,7 +62,7 @@ class ScoringEngineShellTest(base.CommandTestCase):
self.m_se_mgr = mock.Mock()
self.m_se_mgr_cls.return_value = self.m_se_mgr
self.stdout = io.StringIO()
self.stdout = six.StringIO()
self.cmd = shell.WatcherShell(stdout=self.stdout)
def test_do_scoringengine_list(self):

View File

@@ -14,8 +14,8 @@
# limitations under the License.
import datetime
import io
from unittest import mock
import mock
import six
from watcherclient import shell
from watcherclient.tests.unit.v1 import base
@@ -61,7 +61,7 @@ class ServiceShellTest(base.CommandTestCase):
self.m_service_mgr = mock.Mock()
self.m_service_mgr_cls.return_value = self.m_service_mgr
self.stdout = io.StringIO()
self.stdout = six.StringIO()
self.cmd = shell.WatcherShell(stdout=self.stdout)
def test_do_service_list(self):

View File

@@ -107,16 +107,6 @@ fake_responses_sorting = {
},
}
fake_responses_marker = {
'/v1/strategies/?marker=2cf86250-d309-4b81-818e-1537f3dba6e5':
{
'GET': (
{},
{"strategies": [STRATEGY2]}
),
},
}
class StrategyManagerTest(testtools.TestCase):
@@ -141,19 +131,6 @@ class StrategyManagerTest(testtools.TestCase):
self.assertEqual(expect, self.api.calls)
self.assertEqual(1, len(strategies))
def test_strategies_list_marker(self):
self.api = utils.FakeAPI(fake_responses_marker)
self.mgr = watcherclient.v1.strategy.StrategyManager(self.api)
strategies = self.mgr.list(marker=STRATEGY1['uuid'])
expect = [
('GET',
'/v1/strategies/?marker=2cf86250-d309-4b81-818e-1537f3dba6e5',
{},
None),
]
self.assertEqual(expect, self.api.calls)
self.assertEqual(1, len(strategies))
def test_strategies_list_limit(self):
self.api = utils.FakeAPI(fake_responses_pagination)
self.mgr = watcherclient.v1.strategy.StrategyManager(self.api)

View File

@@ -14,8 +14,8 @@
# limitations under the License.
import datetime
import io
from unittest import mock
import mock
import six
from oslo_serialization import jsonutils
@@ -69,7 +69,7 @@ class StrategyShellTest(base.CommandTestCase):
self.m_strategy_mgr = mock.Mock()
self.m_strategy_mgr_cls.return_value = self.m_strategy_mgr
self.stdout = io.StringIO()
self.stdout = six.StringIO()
self.cmd = shell.WatcherShell(stdout=self.stdout)
def test_do_strategy_list(self):
@@ -90,23 +90,6 @@ class StrategyShellTest(base.CommandTestCase):
self.m_strategy_mgr.list.assert_called_once_with(detail=False)
def test_do_strategy_list_marker(self):
strategy2 = resource.Strategy(mock.Mock(), STRATEGY_2)
self.m_strategy_mgr.list.return_value = [strategy2]
exit_code, results = self.run_cmd(
'strategy list --marker 2cf86250-d309-4b81-818e-1537f3dba6e5')
self.assertEqual(0, exit_code)
self.assertEqual(
[self.resource_as_dict(strategy2, self.SHORT_LIST_FIELDS,
self.SHORT_LIST_FIELD_LABELS)],
results)
self.m_strategy_mgr.list.assert_called_once_with(
detail=False,
marker='2cf86250-d309-4b81-818e-1537f3dba6e5')
def test_do_strategy_list_detail(self):
strategy1 = resource.Strategy(mock.Mock(), STRATEGY_1)
strategy2 = resource.Strategy(mock.Mock(), STRATEGY_2)

View File

@@ -17,7 +17,6 @@ from watcherclient.v1 import action
from watcherclient.v1 import action_plan
from watcherclient.v1 import audit
from watcherclient.v1 import audit_template
from watcherclient.v1 import data_model
from watcherclient.v1 import goal
from watcherclient.v1 import scoring_engine
from watcherclient.v1 import service
@@ -39,12 +38,9 @@ Service = service.Service
ServiceManager = service.ServiceManager
Strategy = strategy.Strategy
StrategyManager = strategy.StrategyManager
DataModel = data_model.DataModel
DataModelManager = data_model.DataModelManager
__all__ = (
"Action", "ActionManager", "ActionPlan", "ActionPlanManager",
"Audit", "AuditManager", "AuditTemplate", "AuditTemplateManager",
"Goal", "GoalManager", "ScoringEngine", "ScoringEngineManager",
"Service", "ServiceManager", "Strategy", "StrategyManager",
"DataModel", "DataModelManager")
"Service", "ServiceManager", "Strategy", "StrategyManager")

View File

@@ -83,6 +83,3 @@ class ActionManager(base.Manager):
return self._list(self._path(action_id))[0]
except IndexError:
return None
def update(self, action_id, patch):
return self._update(self._path(action_id), patch)

View File

@@ -13,31 +13,18 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import copy
import io
from cliff.formatters import yaml_format
from osc_lib import utils
from oslo_utils import uuidutils
import six
from watcherclient._i18n import _
from watcherclient.common import api_versioning
from watcherclient.common import command
from watcherclient.common import utils as common_utils
from watcherclient import exceptions
from watcherclient.v1 import resource_fields as res_fields
def drop_unsupported_field(app_args, fields, field_labels):
fields = copy.copy(fields)
field_labels = copy.copy(field_labels)
api_ver = app_args.os_infra_optim_api_version
if not api_versioning.action_update_supported(api_ver):
fields.remove('status_message')
field_labels.remove('Status Message')
return fields, field_labels
def format_global_efficacy(global_efficacy):
formatted_global_eff = {}
for eff in global_efficacy:
@@ -64,7 +51,7 @@ class ShowActionPlan(command.ShowOne):
return parser
def _format_indicators(self, action_plan, parsed_args):
out = io.StringIO()
out = six.StringIO()
efficacy_indicators = action_plan.efficacy_indicators
fields = ['name', 'description', 'value', 'unit']
yaml_format.YAMLFormatter().emit_list(
@@ -79,7 +66,7 @@ class ShowActionPlan(command.ShowOne):
def _format_global_efficacy(self, global_efficacy, parsed_args):
formatted_global_efficacy = format_global_efficacy(global_efficacy)
out = io.StringIO()
out = six.StringIO()
yaml_format.YAMLFormatter().emit_one(
column_names=list(resource.capitalize()
for resource in formatted_global_efficacy),
@@ -113,8 +100,6 @@ class ShowActionPlan(command.ShowOne):
columns = res_fields.ACTION_PLAN_FIELDS
column_headers = res_fields.ACTION_PLAN_FIELD_LABELS
columns, column_headers = drop_unsupported_field(
self.app_args, columns, column_headers)
return column_headers, utils.get_item_properties(action_plan, columns)
@@ -158,7 +143,7 @@ class ListActionPlan(command.Lister):
return parser
def _format_indicators(self, action_plan, parsed_args):
out = io.StringIO()
out = six.StringIO()
efficacy_indicators = action_plan.efficacy_indicators
fields = ['name', 'value', 'unit']
yaml_format.YAMLFormatter().emit_list(
@@ -173,7 +158,7 @@ class ListActionPlan(command.Lister):
def _format_global_efficacy(self, global_efficacy, parsed_args):
formatted_global_efficacy = format_global_efficacy(global_efficacy)
out = io.StringIO()
out = six.StringIO()
yaml_format.YAMLFormatter().emit_one(
column_names=list(resource.capitalize()
for resource in formatted_global_efficacy),
@@ -192,8 +177,6 @@ class ListActionPlan(command.Lister):
if parsed_args.detail:
fields = res_fields.ACTION_PLAN_FIELDS
field_labels = res_fields.ACTION_PLAN_FIELD_LABELS
fields, field_labels = drop_unsupported_field(
self.app_args, fields, field_labels)
else:
fields = res_fields.ACTION_PLAN_SHORT_LIST_FIELDS
field_labels = res_fields.ACTION_PLAN_SHORT_LIST_FIELD_LABELS
@@ -255,8 +238,6 @@ class UpdateActionPlan(command.ShowOne):
columns = res_fields.ACTION_PLAN_FIELDS
column_headers = res_fields.ACTION_PLAN_FIELD_LABELS
columns, column_headers = drop_unsupported_field(
self.app_args, columns, column_headers)
return column_headers, utils.get_item_properties(action_plan, columns)
@@ -283,8 +264,6 @@ class StartActionPlan(command.ShowOne):
columns = res_fields.ACTION_PLAN_FIELDS
column_headers = res_fields.ACTION_PLAN_FIELD_LABELS
columns, column_headers = drop_unsupported_field(
self.app_args, columns, column_headers)
return column_headers, utils.get_item_properties(action_plan, columns)
@@ -334,7 +313,5 @@ class CancelActionPlan(command.ShowOne):
columns = res_fields.ACTION_PLAN_FIELDS
column_headers = res_fields.ACTION_PLAN_FIELD_LABELS
columns, column_headers = drop_unsupported_field(
self.app_args, columns, column_headers)
return column_headers, utils.get_item_properties(action_plan, columns)

View File

@@ -13,29 +13,15 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import copy
from osc_lib import utils
from oslo_utils import uuidutils
from watcherclient._i18n import _
from watcherclient.common import api_versioning
from watcherclient.common import command
from watcherclient.common import utils as common_utils
from watcherclient import exceptions
from watcherclient.v1 import resource_fields as res_fields
def drop_unsupported_field(app_args, fields, field_labels):
fields = copy.copy(fields)
field_labels = copy.copy(field_labels)
api_ver = app_args.os_infra_optim_api_version
if not api_versioning.action_update_supported(api_ver):
fields.remove('status_message')
field_labels.remove('Status Message')
return fields, field_labels
class ShowAction(command.ShowOne):
"""Show detailed information about a given action."""
@@ -58,8 +44,6 @@ class ShowAction(command.ShowOne):
columns = res_fields.ACTION_FIELDS
column_headers = res_fields.ACTION_FIELD_LABELS
columns, column_headers = drop_unsupported_field(
self.app_args, columns, column_headers)
return column_headers, utils.get_item_properties(action, columns)
@@ -120,8 +104,6 @@ class ListAction(command.Lister):
if parsed_args.detail:
fields = res_fields.ACTION_FIELDS
field_labels = res_fields.ACTION_FIELD_LABELS
fields, field_labels = drop_unsupported_field(
self.app_args, fields, field_labels)
else:
fields = res_fields.ACTION_SHORT_LIST_FIELDS
field_labels = res_fields.ACTION_SHORT_LIST_FIELD_LABELS
@@ -137,67 +119,3 @@ class ListAction(command.Lister):
return (field_labels,
(utils.get_item_properties(item, fields) for item in data))
class UpdateAction(command.ShowOne):
"""Update action command."""
def get_parser(self, prog_name):
parser = super(UpdateAction, self).get_parser(prog_name)
parser.add_argument(
'action',
metavar='<action>',
help=_('UUID of the action'))
parser.add_argument(
'--state',
metavar='<state>',
help=_('New state for the action (e.g., SKIPPED)'))
parser.add_argument(
'--reason',
metavar='<reason>',
help=_('Reason for the state change'))
return parser
def take_action(self, parsed_args):
client = getattr(self.app.client_manager, "infra-optim")
# Check if action update is supported in the requested API version
api_ver = self.app_args.os_infra_optim_api_version
if not api_versioning.action_update_supported(api_ver):
raise exceptions.CommandError(
_("Action update is not supported in API version %s. "
"Minimum required version is 1.5.") % api_ver)
if not parsed_args.state and not parsed_args.reason:
raise exceptions.CommandError(
_("At least one field update is required for this operation"))
if not uuidutils.is_uuid_like(parsed_args.action):
raise exceptions.ValidationError()
patch = []
if parsed_args.state:
patch.append({
'op': 'replace',
'path': '/state',
'value': parsed_args.state
})
if parsed_args.reason:
patch.append({
'op': 'replace',
'path': '/status_message',
'value': parsed_args.reason
})
try:
action = client.action.update(parsed_args.action, patch)
except exceptions.HTTPNotFound as exc:
raise exceptions.CommandError(str(exc))
columns = res_fields.ACTION_FIELDS
column_headers = res_fields.ACTION_FIELD_LABELS
columns, column_headers = drop_unsupported_field(
self.app_args, columns, column_headers)
return column_headers, utils.get_item_properties(action, columns)

View File

@@ -20,7 +20,7 @@ from watcherclient import exceptions as exc
CREATION_ATTRIBUTES = ['audit_template_uuid', 'audit_type', 'interval',
'parameters', 'goal', 'strategy', 'auto_trigger',
'name', 'start_time', 'end_time', 'force']
'name']
class Audit(base.Resource):

View File

@@ -13,37 +13,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import copy
from osc_lib import utils
from oslo_utils import uuidutils
from watcherclient._i18n import _
from watcherclient.common import api_versioning
from watcherclient.common import command
from watcherclient.common import utils as common_utils
from watcherclient import exceptions
from watcherclient.v1 import resource_fields as res_fields
def drop_unsupported_field(app_args, fields, field_labels):
fields = copy.copy(fields)
field_labels = copy.copy(field_labels)
api_ver = app_args.os_infra_optim_api_version
if not api_versioning.allow_start_end_audit_time(api_ver):
for field, label in zip(('start_time', 'end_time'),
('Start Time', 'End Time')):
fields.remove(field)
field_labels.remove(label)
if not api_versioning.launch_audit_forced(api_ver):
fields.remove('force')
field_labels.remove('Force')
if not api_versioning.action_update_supported(api_ver):
fields.remove('status_message')
field_labels.remove('Status Message')
return fields, field_labels
class ShowAudit(command.ShowOne):
"""Show detailed information about a given audit."""
@@ -68,8 +47,6 @@ class ShowAudit(command.ShowOne):
columns = res_fields.AUDIT_FIELDS
column_headers = res_fields.AUDIT_FIELD_LABELS
columns, column_headers = drop_unsupported_field(
self.app_args, columns, column_headers)
return column_headers, utils.get_item_properties(audit, columns)
@@ -141,10 +118,6 @@ class ListAudit(command.Lister):
fields = res_fields.AUDIT_SHORT_LIST_FIELDS
field_labels = res_fields.AUDIT_SHORT_LIST_FIELD_LABELS
if parsed_args.detail:
fields, field_labels = drop_unsupported_field(
self.app_args, fields, field_labels)
params.update(common_utils.common_params_for_list(
parsed_args, fields, field_labels))
@@ -170,8 +143,8 @@ class CreateAudit(command.ShowOne):
dest='audit_type',
metavar='<audit_type>',
default='ONESHOT',
choices=['ONESHOT', 'CONTINUOUS', 'EVENT'],
help=_("Audit type. It must be ONESHOT, CONTINUOUS or EVENT. "
choices=['ONESHOT', 'CONTINUOUS'],
help=_("Audit type. It must be ONESHOT or CONTINUOUS. "
"Default is ONESHOT."))
parser.add_argument(
'-p', '--parameter',
@@ -185,7 +158,7 @@ class CreateAudit(command.ShowOne):
dest='interval',
metavar='<interval>',
help=_('Audit interval (in seconds or cron format). '
'Cron interval can be used like: ``*/5 * * * *``. '
'Cron inteval can be used like: "*/5 * * * *". '
'Only used if the audit is CONTINUOUS.'))
parser.add_argument(
'-g', '--goal',
@@ -214,24 +187,6 @@ class CreateAudit(command.ShowOne):
dest='name',
metavar='<name>',
help=_('Name for this audit.'))
parser.add_argument(
'--start-time',
dest='start_time',
metavar='<start_time>',
help=_('CONTINUOUS audit local start time. '
'Format: YYYY-MM-DD hh:mm:ss'))
parser.add_argument(
'--end-time',
dest='end_time',
metavar='<end_time>',
help=_('CONTINUOUS audit local end time. '
'Format: YYYY-MM-DD hh:mm:ss'))
parser.add_argument(
'--force',
dest='force',
action='store_true',
help=_('Launch audit even if action plan '
'is ongoing. default is False'))
return parser
@@ -239,27 +194,25 @@ class CreateAudit(command.ShowOne):
client = getattr(self.app.client_manager, "infra-optim")
field_list = ['audit_template_uuid', 'audit_type', 'parameters',
'interval', 'goal', 'strategy', 'auto_trigger',
'name']
api_ver = self.app_args.os_infra_optim_api_version
if api_versioning.allow_start_end_audit_time(api_ver):
if parsed_args.start_time is not None:
field_list.append('start_time')
if parsed_args.end_time is not None:
field_list.append('end_time')
if api_versioning.launch_audit_forced(api_ver):
if parsed_args.force is not None:
field_list.append('force')
'interval', 'goal', 'strategy', 'auto_trigger', 'name']
fields = dict((k, v) for (k, v) in vars(parsed_args).items()
if k in field_list and v is not None)
fields = common_utils.args_array_to_dict(fields, 'parameters')
if fields.get('goal'):
if not uuidutils.is_uuid_like(fields['goal']):
fields['goal'] = client.goal.get(fields['goal']).uuid
if fields.get('audit_template_uuid'):
if not uuidutils.is_uuid_like(fields['audit_template_uuid']):
fields['audit_template_uuid'] = client.audit_template.get(
fields['audit_template_uuid']).uuid
# optional
if fields.get('strategy'):
if not uuidutils.is_uuid_like(fields['strategy']):
fields['strategy'] = client.strategy.get(
fields['strategy']).uuid
audit = client.audit.create(**fields)
if audit.strategy_name is None:
@@ -267,8 +220,6 @@ class CreateAudit(command.ShowOne):
columns = res_fields.AUDIT_FIELDS
column_headers = res_fields.AUDIT_FIELD_LABELS
columns, column_headers = drop_unsupported_field(
self.app_args, columns, column_headers)
return column_headers, utils.get_item_properties(audit, columns)
@@ -310,9 +261,6 @@ class UpdateAudit(command.ShowOne):
columns = res_fields.AUDIT_FIELDS
column_headers = res_fields.AUDIT_FIELD_LABELS
columns, column_headers = drop_unsupported_field(
self.app_args, columns, column_headers)
return column_headers, utils.get_item_properties(audit, columns)

View File

@@ -167,8 +167,8 @@ class CreateAuditTemplate(command.ShowOne):
metavar='<path>',
help=_("Part of the cluster on which an audit will be done.\n"
"Can be provided either in yaml or json file.\n"
"YAML example::\n"
"\n"
"YAML example:\n"
"---\n"
" - compute:\n"
" - host_aggregates:\n"
" - id: 1\n"
@@ -201,49 +201,48 @@ class CreateAuditTemplate(command.ShowOne):
" - uuid: UUID1\n"
" - uuid: UUID2\n"
"\n"
"JSON example::\n"
"\n"
" [\n"
" {\"compute\":\n"
" [{\"host_aggregates\": [\n"
" {\"id\": 1},\n"
" {\"id\": 2},\n"
" {\"id\": 3}]},\n"
" {\"availability_zones\": [\n"
" {\"name\": \"AZ1\"},\n"
" {\"name\": \"AZ2\"}]},\n"
" {\"exclude\": [\n"
" {\"instances\": [\n"
" {\"uuid\": \"UUID1\"},\n"
" {\"uuid\": \"UUID2\"}\n"
" ]},\n"
" {\"compute_nodes\": [\n"
" {\"name\": \"compute1\"}\n"
" ]}\n"
" ]}]\n"
" },\n"
" {\"storage\":\n"
" [{\"availability_zones\": [\n"
" {\"name\": \"AZ1\"},\n"
" {\"name\": \"AZ2\"}]},\n"
" {\"volume_types\": [\n"
" {\"name\": \"lvm1\"},\n"
" {\"name\": \"lvm2\"}]},\n"
" {\"exclude\": [\n"
" {\"storage_pools\": [\n"
" {\"name\": \"host0@backend0#pool0\"},\n"
" {\"name\": \"host1@backend1#pool1\"}\n"
" ]},\n"
" {\"volumes\": [\n"
" {\"uuid\": \"UUID1\"},\n"
" {\"uuid\": \"UUID2\"}\n"
" ]},\n"
" {\"projects\": [\n"
" {\"uuid\": \"UUID1\"},\n"
" {\"uuid\": \"UUID2\"}\n"
" ]},\n"
" ]}]\n"
" }\n"
"JSON example:\n"
"[\n"
" {\"compute\":\n"
" [{\"host_aggregates\": [\n"
" {\"id\": 1},\n"
" {\"id\": 2},\n"
" {\"id\": 3}]},\n"
" {\"availability_zones\": [\n"
" {\"name\": \"AZ1\"},\n"
" {\"name\": \"AZ2\"}]},\n"
" {\"exclude\": [\n"
" {\"instances\": [\n"
" {\"uuid\": \"UUID1\"},\n"
" {\"uuid\": \"UUID2\"}\n"
" ]},\n"
" {\"compute_nodes\": [\n"
" {\"name\": \"compute1\"}\n"
" ]}\n"
" ]}]\n"
" },\n"
" {\"storage\":\n"
" [{\"availability_zones\": [\n"
" {\"name\": \"AZ1\"},\n"
" {\"name\": \"AZ2\"}]},\n"
" {\"volume_types\": [\n"
" {\"name\": \"lvm1\"},\n"
" {\"name\": \"lvm2\"}]},\n"
" {\"exclude\": [\n"
" {\"storage_pools\": [\n"
" {\"name\": \"host0@backend0#pool0\"},\n"
" {\"name\": \"host1@backend1#pool1\"}\n"
" ]},\n"
" {\"volumes\": [\n"
" {\"uuid\": \"UUID1\"},\n"
" {\"uuid\": \"UUID2\"}\n"
" ]},\n"
" {\"projects\": [\n"
" {\"uuid\": \"UUID1\"},\n"
" {\"uuid\": \"UUID2\"}\n"
" ]},\n"
" ]}]\n"
" }\n"
" ]\n"
)
)

View File

@@ -31,19 +31,19 @@ class Client(object):
def __init__(self, endpoint=None, *args, **kwargs):
"""Initialize a new client for the Watcher v1 API."""
if kwargs.get('os_infra_optim_api_version'):
if kwargs.get('os_watcher_api_version'):
kwargs['api_version_select_state'] = "user"
else:
if not endpoint:
raise exceptions.EndpointException(
_("Must provide 'endpoint' if os_infra_optim_api_version "
_("Must provide 'endpoint' if os_watcher_api_version "
"isn't specified"))
# If the user didn't specify a version, use a cached version if
# one has been stored
host, netport = httpclient.get_server(endpoint)
kwargs['api_version_select_state'] = "default"
kwargs['os_infra_optim_api_version'] = httpclient.DEFAULT_VER
kwargs['os_watcher_api_version'] = httpclient.DEFAULT_VER
self.http_client = httpclient._construct_http_client(
endpoint, *args, **kwargs)
@@ -56,4 +56,3 @@ class Client(object):
self.scoring_engine = v1.ScoringEngineManager(self.http_client)
self.service = v1.ServiceManager(self.http_client)
self.strategy = v1.StrategyManager(self.http_client)
self.data_model = v1.DataModelManager(self.http_client)

View File

@@ -1,56 +0,0 @@
# Copyright 2019 ZTE 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.
from watcherclient.common import base
from watcherclient.common import utils
class DataModel(base.Resource):
def __repr__(self):
return "<DataModel %s>" % self._info
class DataModelManager(base.Manager):
resource_class = DataModel
@staticmethod
def _path(filters=None):
if filters:
path = '/v1/data_model/%s' % filters
else:
path = '/v1/data_model'
return path
def list(self, data_model_type='compute', audit=None):
"""Retrieve a list of data model.
:param data_model_type: The type of data model user wants to list.
Supported values: compute.
Future support values: storage, baremetal.
The default value is compute.
:param audit: The UUID of the audit, used to filter data model
by the scope in audit.
:returns: A list of data model.
"""
path = ''
filters = utils.common_filters()
if audit:
filters.append('audit_uuid=%s' % audit)
filters.append('data_model_type=%s' % data_model_type)
path += '?' + '&'.join(filters)
return self._list(self._path(path))[0]

View File

@@ -1,77 +0,0 @@
# Copyright 2019 ZTE 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.
from watcherclient._i18n import _
from watcherclient.common import command
from watcherclient import exceptions
from watcherclient.v1 import resource_fields as res_fields
class ListDataModel(command.Lister):
"""List information on retrieved data model."""
def get_parser(self, prog_name):
parser = super(ListDataModel, self).get_parser(prog_name)
parser.add_argument(
'--type',
metavar='<type>',
dest='type',
help=_('Type of Datamodel user want to list. '
'Supported values: compute. '
'Future support values: storage, baremetal. '
'Default type is compute.'))
parser.add_argument(
'--audit',
metavar='<audit>',
dest='audit',
help=_('UUID of the audit'))
parser.add_argument(
'--detail',
dest='detail',
action='store_true',
default=False,
help=_("Show detailed information about data model."))
return parser
def get_tuple(self, dic, fields):
ret_tup = []
for item in fields:
ret_tup.append(dic.get(item))
return tuple(ret_tup)
def take_action(self, parsed_args):
client = getattr(self.app.client_manager, "infra-optim")
allowed_type = ['compute', 'storage', 'baremetal']
params = {}
if parsed_args.audit:
params["audit"] = parsed_args.audit
if parsed_args.type:
if parsed_args.type not in allowed_type:
raise exceptions.CommandError(
'Type %s error, '
'Please check the valid type!' % parsed_args.type)
params["data_model_type"] = parsed_args.type
try:
data_model = client.data_model.list(**params)
except exceptions.HTTPNotFound as exc:
raise exceptions.CommandError(str(exc))
# TODO(chenker) Add Storage MODEL_FIELDS when using Storage Datamodel.
if parsed_args.detail:
fields = res_fields.COMPUTE_MODEL_LIST_FIELDS
field_labels = res_fields.COMPUTE_MODEL_LIST_FIELD_LABELS
else:
fields = res_fields.COMPUTE_MODEL_SHORT_LIST_FIELDS
field_labels = res_fields.COMPUTE_MODEL_SHORT_LIST_FIELD_LABELS
return (field_labels,
(self.get_tuple(item, fields) for item in data_model.context))

View File

@@ -29,8 +29,7 @@ class GoalManager(base.Manager):
def _path(goal=None):
return '/v1/goals/%s' % goal if goal else '/v1/goals'
def list(self, limit=None, sort_key=None, sort_dir=None, detail=False,
marker=None):
def list(self, limit=None, sort_key=None, sort_dir=None, detail=False):
"""Retrieve a list of goal.
:param limit: The maximum number of results to return per
@@ -50,15 +49,13 @@ class GoalManager(base.Manager):
:param detail: Optional, boolean whether to return detailed information
about audits.
:param marker: Optional, UUID of the last goal in the previous page.
:returns: A list of goals.
:returns: A list of audits.
"""
if limit is not None:
limit = int(limit)
filters = utils.common_filters(limit, sort_key, sort_dir, marker)
filters = utils.common_filters(limit, sort_key, sort_dir)
path = ''
if detail:
path += 'detail'

View File

@@ -14,9 +14,8 @@
# License for the specific language governing permissions and limitations
# under the License.
import io
from osc_lib import utils
import six
from watcherclient._i18n import _
from watcherclient.common import command
@@ -38,7 +37,7 @@ class ShowGoal(command.ShowOne):
return parser
def _format_indicator_spec_table(self, spec, parsed_args):
out = io.StringIO()
out = six.StringIO()
self.formatter.emit_one(
column_names=list(field.capitalize() for field in spec.keys()),
data=utils.get_dict_properties(spec, spec.keys()),
@@ -97,18 +96,11 @@ class ListGoal(command.Lister):
metavar='<direction>',
choices=['asc', 'desc'],
help=_('Sort direction: "asc" (the default) or "desc".'))
parser.add_argument(
'--marker',
dest='marker',
metavar='<marker>',
default=None,
help=_('UUID of the last goal in the previous page; '
'displays list of goals after "marker".'))
return parser
def _format_indicator_spec_table(self, goal, parsed_args):
out = io.StringIO()
out = six.StringIO()
efficacy_specification = goal.efficacy_specification
fields = ['name', 'unit']
self.formatter.emit_list(

37
watcherclient/v1/resource_fields.py Normal file → Executable file
View File

@@ -33,14 +33,12 @@ AUDIT_TEMPLATE_SHORT_LIST_FIELD_LABELS = ['UUID', 'Name', 'Goal', 'Strategy']
AUDIT_FIELDS = ['uuid', 'name', 'created_at', 'updated_at', 'deleted_at',
'state', 'audit_type', 'parameters', 'interval', 'goal_name',
'strategy_name', 'scope', 'auto_trigger', 'next_run_time',
'hostname', 'start_time', 'end_time', 'force',
'status_message']
'hostname']
AUDIT_FIELD_LABELS = ['UUID', 'Name', 'Created At', 'Updated At', 'Deleted At',
'State', 'Audit Type', 'Parameters', 'Interval', 'Goal',
'Strategy', 'Audit Scope', 'Auto Trigger',
'Next Run Time', 'Hostname', 'Start Time', 'End Time',
'Force', 'Status Message']
'Next Run Time', 'Hostname']
AUDIT_SHORT_LIST_FIELDS = ['uuid', 'name', 'audit_type',
'state', 'goal_name', 'strategy_name',
@@ -52,13 +50,12 @@ AUDIT_SHORT_LIST_FIELD_LABELS = ['UUID', 'Name', 'Audit Type', 'State', 'Goal',
# Action Plan
ACTION_PLAN_FIELDS = ['uuid', 'created_at', 'updated_at', 'deleted_at',
'audit_uuid', 'strategy_name', 'state',
'efficacy_indicators', 'global_efficacy', 'hostname',
'status_message']
'efficacy_indicators', 'global_efficacy', 'hostname']
ACTION_PLAN_FIELD_LABELS = ['UUID', 'Created At', 'Updated At', 'Deleted At',
'Audit', 'Strategy', 'State',
'Efficacy indicators', 'Global efficacy',
'Hostname', 'Status Message']
'Hostname']
ACTION_PLAN_SHORT_LIST_FIELDS = ['uuid', 'audit_uuid', 'state',
'updated_at', 'global_efficacy']
@@ -71,11 +68,11 @@ GLOBAL_EFFICACY_FIELDS = ['value', 'unit', 'name', 'description']
# Action
ACTION_FIELDS = ['uuid', 'created_at', 'updated_at', 'deleted_at', 'parents',
'state', 'action_plan_uuid', 'action_type',
'input_parameters', 'description', 'status_message']
'input_parameters', 'description']
ACTION_FIELD_LABELS = ['UUID', 'Created At', 'Updated At', 'Deleted At',
'Parents', 'State', 'Action Plan', 'Action',
'Parameters', 'Description', 'Status Message']
'Parameters', 'Description']
ACTION_SHORT_LIST_FIELDS = ['uuid', 'parents',
'state', 'action_plan_uuid', 'action_type']
@@ -100,28 +97,6 @@ STRATEGY_FIELDS = ['uuid', 'name', 'display_name', 'goal_name',
STRATEGY_FIELD_LABELS = ['UUID', 'Name', 'Display name', 'Goal',
'Parameters spec']
# Data Model
COMPUTE_MODEL_LIST_FIELDS = [
'server_uuid', 'server_name', 'server_vcpus',
'server_memory', 'server_disk', 'server_state', 'node_uuid',
'node_hostname', 'node_vcpus', 'node_vcpu_ratio', 'node_memory',
'node_memory_ratio', 'node_disk', 'node_disk_ratio', 'node_state']
COMPUTE_MODEL_LIST_FIELD_LABELS = [
'Server_UUID', 'Server Name', 'Server Vcpus',
'Server Memory', 'Server Disk', 'Server State', 'Node UUID',
'Node Host Name', 'Node Vcpus', 'Node Vcpu Ratio', 'Node Memory',
'Node Memory Ratio', 'Node Disk', 'Node Disk Ratio', 'Node State']
COMPUTE_MODEL_SHORT_LIST_FIELDS = [
'server_uuid', 'server_name',
'server_state', 'node_uuid', 'node_hostname']
COMPUTE_MODEL_SHORT_LIST_FIELD_LABELS = [
'Server UUID', 'Server Name',
'Server State', 'Node UUID', 'Node Host Name']
STRATEGY_SHORT_LIST_FIELDS = ['uuid', 'name', 'display_name', 'goal_name']
STRATEGY_SHORT_LIST_FIELD_LABELS = ['UUID', 'Name', 'Display name', 'Goal']

View File

@@ -13,7 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
from urllib import parse
import six.moves.urllib.parse as parse
from watcherclient.common import base
from watcherclient.common import utils
@@ -38,7 +38,7 @@ class StrategyManager(base.Manager):
return path
def list(self, goal=None, limit=None, sort_key=None,
sort_dir=None, detail=False, marker=None):
sort_dir=None, detail=False):
"""Retrieve a list of strategy.
:param goal: The UUID of the goal to filter by
@@ -58,15 +58,14 @@ class StrategyManager(base.Manager):
:param detail: Optional, boolean whether to return detailed information
about audits.
:param marker: Optional, UUID of the last strategy in the previous
page.
:returns: A list of audits.
"""
if limit is not None:
limit = int(limit)
filters = utils.common_filters(limit, sort_key, sort_dir, marker)
filters = utils.common_filters(limit, sort_key, sort_dir)
if goal:
filters.append(parse.urlencode(dict(goal=goal)))

View File

@@ -71,7 +71,7 @@ class StateStrategy(command.Lister):
def _format_spec(self, requirements):
for req in requirements:
if isinstance(req.state, list):
if type(req.state) == list:
req.state = jsonutils.dumps(req.state, indent=2)
return requirements
@@ -123,13 +123,7 @@ class ListStrategy(command.Lister):
metavar='<direction>',
choices=['asc', 'desc'],
help='Sort direction: "asc" (the default) or "desc".')
parser.add_argument(
'--marker',
dest='marker',
metavar='<marker>',
default=None,
help=_('UUID of the last strategy in the previous page; '
'displays list of strategies after "marker".'))
return parser
def take_action(self, parsed_args):