summaryrefslogtreecommitdiff
diff options
authorThomas Goirand <zigo@debian.org>2019-09-17 10:06:03 +0200
committergit-ubuntu importer <ubuntu-devel-discuss@lists.ubuntu.com>2019-09-17 16:28:27 +0000
commit54fa99c6d9cd3ce69b879088cdef1da68698ded8 (patch)
tree8620057aca06a8640ea3b45f7428bd65b823a7d7
parentfd1fc7c96d10e5f26e5f1b7b0485b486e6218acd (diff)
parent4325b25e160b4577395b85c73177899a153f29ca (diff)
2.4.0-1 (patches applied)applied/2.4.0-1
Imported using git-ubuntu import.
-rw-r--r--.coveragerc11
-rw-r--r--.gitignore7
-rw-r--r--.gitreview2
-rw-r--r--.stestr.conf2
-rw-r--r--.zuul.yaml7
-rw-r--r--README.rst2
-rw-r--r--debian/changelog11
-rw-r--r--debian/compat1
-rw-r--r--debian/control4
-rw-r--r--doc/requirements.txt7
-rw-r--r--doc/source/cli/details.rst36
-rw-r--r--doc/source/cli/index.rst2
-rw-r--r--doc/source/cli/openstack_cli.rst2
-rw-r--r--doc/source/cli/watcher.rst4
-rw-r--r--doc/source/contributing.rst6
-rw-r--r--doc/source/installation.rst2
-rw-r--r--lower-constraints.txt1
-rw-r--r--requirements.txt2
-rw-r--r--setup.cfg12
-rw-r--r--test-requirements.txt4
-rw-r--r--tox.ini38
-rw-r--r--watcherclient/__init__.py9
-rw-r--r--watcherclient/client.py13
-rw-r--r--watcherclient/common/api_versioning.py22
-rw-r--r--watcherclient/common/base.py5
-rw-r--r--watcherclient/common/cliutils.py278
-rw-r--r--watcherclient/common/httpclient.py128
-rw-r--r--watcherclient/config.py23
-rw-r--r--watcherclient/osc/plugin.py41
-rw-r--r--watcherclient/plugin.py34
-rw-r--r--watcherclient/shell.py14
-rw-r--r--watcherclient/tests/README.rst61
-rw-r--r--watcherclient/tests/functional/__init__.py0
-rw-r--r--watcherclient/tests/functional/v1/__init__.py0
-rw-r--r--watcherclient/tests/functional/v1/base.py144
-rw-r--r--watcherclient/tests/functional/v1/test_action.py81
-rw-r--r--watcherclient/tests/functional/v1/test_action_plan.py97
-rw-r--r--watcherclient/tests/functional/v1/test_audit.py136
-rw-r--r--watcherclient/tests/functional/v1/test_audit_template.py89
-rw-r--r--watcherclient/tests/functional/v1/test_goal.py41
-rw-r--r--watcherclient/tests/functional/v1/test_scoring_engine.py40
-rw-r--r--watcherclient/tests/functional/v1/test_service.py47
-rw-r--r--watcherclient/tests/functional/v1/test_strategy.py48
-rw-r--r--watcherclient/tests/unit/test_client.py20
-rw-r--r--watcherclient/tests/unit/v1/base.py6
-rw-r--r--watcherclient/tests/unit/v1/test_action_plan.py86
-rw-r--r--watcherclient/tests/unit/v1/test_action_plan_shell.py38
-rwxr-xr-xwatcherclient/tests/unit/v1/test_audit_shell.py377
-rw-r--r--watcherclient/tests/unit/v1/test_data_model.py75
-rw-r--r--watcherclient/tests/unit/v1/test_data_model_shell.py129
-rw-r--r--watcherclient/tests/unit/v1/test_goal.py21
-rw-r--r--watcherclient/tests/unit/v1/test_goal_shell.py17
-rw-r--r--watcherclient/tests/unit/v1/test_strategy.py23
-rw-r--r--watcherclient/tests/unit/v1/test_strategy_shell.py17
-rw-r--r--watcherclient/v1/__init__.py6
-rw-r--r--watcherclient/v1/audit.py2
-rw-r--r--watcherclient/v1/audit_shell.py60
-rw-r--r--watcherclient/v1/client.py7
-rw-r--r--watcherclient/v1/data_model.py56
-rw-r--r--watcherclient/v1/data_model_shell.py77
-rw-r--r--watcherclient/v1/goal.py9
-rw-r--r--watcherclient/v1/goal_shell.py7
-rwxr-xr-xwatcherclient/v1/resource_fields.py27
-rw-r--r--watcherclient/v1/strategy.py7
-rw-r--r--watcherclient/v1/strategy_shell.py8
65 files changed, 1232 insertions, 1357 deletions
diff --git a/.coveragerc b/.coveragerc
new file mode 100644
index 0000000..9a7dc36
--- /dev/null
+++ b/.coveragerc
@@ -0,0 +1,11 @@
+[run]
+branch = True
+source = watcherclient
+omit =
+ watcherclient/tests/*
+
+[report]
+ignore_errors = True
+exclude_lines =
+ @abc.abstract
+ raise NotImplementedError
diff --git a/.gitignore b/.gitignore
index 7052a54..3fb83c5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,11 +23,12 @@ lib64
pip-log.txt
# Unit test / coverage reports
-.coverage
+.coverage*
.tox
nosetests.xml
.stestr/
.venv
+.testrepository/
# Translations
*.mo
@@ -56,6 +57,10 @@ ChangeLog
sftp-config.json
/.idea/
+/cover/
# Desktop Service Store
*.DS_Store
+
+# Atom
+.remote-sync.json
diff --git a/.gitreview b/.gitreview
index abcd52f..034240a 100644
--- a/.gitreview
+++ b/.gitreview
@@ -1,4 +1,4 @@
[gerrit]
-host=review.openstack.org
+host=review.opendev.org
port=29418
project=openstack/python-watcherclient.git
diff --git a/.stestr.conf b/.stestr.conf
index 7452f58..14b8dcc 100644
--- a/.stestr.conf
+++ b/.stestr.conf
@@ -1,3 +1,3 @@
[DEFAULT]
-test_path=./watcherclient/tests/functional
+test_path=./watcherclient/tests/unit
top_dir=./
diff --git a/.zuul.yaml b/.zuul.yaml
index b6628e0..0ac5d06 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -1,13 +1,12 @@
- project:
templates:
+ - openstack-cover-jobs
- openstack-lower-constraints-jobs
- openstack-python-jobs
- - openstack-python35-jobs
- - openstack-python36-jobs
+ - openstack-python3-train-jobs
- publish-openstack-docs-pti
- check-requirements
- openstackclient-plugin-jobs
check:
jobs:
- - watcherclient-tempest-functional:
- voting: false
+ - watcherclient-tempest-functional
diff --git a/README.rst b/README.rst
index d8ba338..741ab0e 100644
--- a/README.rst
+++ b/README.rst
@@ -24,7 +24,7 @@ migration, increased energy efficiency and more!
* Free software: Apache license
* Wiki: https://wiki.openstack.org/wiki/Watcher
-* Source: https://git.openstack.org/cgit/openstack/python-watcher
+* Source: https://opendev.org/openstack/python-watcherclient
* Bugs: https://bugs.launchpad.net/watcher
Installation
diff --git a/debian/changelog b/debian/changelog
index dcb5e25..3439153 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,14 @@
+python-watcherclient (2.4.0-1) experimental; urgency=medium
+
+ [ Ondřej Nový ]
+ * Use debhelper-compat instead of debian/compat.
+ * Bump Standards-Version to 4.4.0.
+
+ [ Thomas Goirand ]
+ * New upstream release.
+
+ -- Thomas Goirand <zigo@debian.org> Tue, 17 Sep 2019 10:06:03 +0200
+
python-watcherclient (2.2.0-2) unstable; urgency=medium
* Uploading to unstable.
diff --git a/debian/compat b/debian/compat
deleted file mode 100644
index f599e28..0000000
--- a/debian/compat
+++ /dev/null
@@ -1 +0,0 @@
-10
diff --git a/debian/control b/debian/control
index 3219d70..b42966b 100644
--- a/debian/control
+++ b/debian/control
@@ -6,7 +6,7 @@ Uploaders:
Thomas Goirand <zigo@debian.org>,
Michal Arbet <michal.arbet@ultimum.io>,
Build-Depends:
- debhelper (>= 10),
+ debhelper-compat (= 10),
dh-python,
openstack-pkg-tools,
python3-all,
@@ -36,7 +36,7 @@ Build-Depends-Indep:
python3-testtools,
python3-yaml,
subunit,
-Standards-Version: 4.3.0
+Standards-Version: 4.4.0
Vcs-Browser: https://salsa.debian.org/openstack-team/clients/python-watcherclient
Vcs-Git: https://salsa.debian.org/openstack-team/clients/python-watcherclient.git
Homepage: https://github.com/openstack/python-watcherclient
diff --git a/doc/requirements.txt b/doc/requirements.txt
new file mode 100644
index 0000000..71d9dcd
--- /dev/null
+++ b/doc/requirements.txt
@@ -0,0 +1,7 @@
+# The order of packages is significant, because pip processes them in the order
+# of appearance. Changing the order has an impact on the overall integration
+# process, which may cause wedges in the gate later.
+
+openstackdocstheme>=1.18.1 # Apache-2.0
+sphinx!=1.6.6,!=1.6.7,>=1.6.5,<2.0.0;python_version=='2.7' # BSD
+sphinx!=1.6.6,!=1.6.7,>=1.6.5;python_version>='3.4' # BSD
diff --git a/doc/source/cli/details.rst b/doc/source/cli/details.rst
index 04b5917..2128cb4 100644
--- a/doc/source/cli/details.rst
+++ b/doc/source/cli/details.rst
@@ -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_WATCHER_API_VERSION]``.
+ Defaults to ``env[OS_INFRA_OPTIM_API_VERSION]``.
``--os-endpoint-type OS_ENDPOINT_TYPE``
Defaults to ``env[OS_ENDPOINT_TYPE]`` or "publicURL"
@@ -969,6 +969,40 @@ 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
diff --git a/doc/source/cli/index.rst b/doc/source/cli/index.rst
index 9a3c22a..74e2169 100644
--- a/doc/source/cli/index.rst
+++ b/doc/source/cli/index.rst
@@ -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/v2.0
+ export OS_AUTH_URL=http://auth.example.com:5000/v3/
The command line tool will attempt to reauthenticate using your
provided credentials for every request. You can override this behavior
diff --git a/doc/source/cli/openstack_cli.rst b/doc/source/cli/openstack_cli.rst
index f285c91..f1e1a97 100644
--- a/doc/source/cli/openstack_cli.rst
+++ b/doc/source/cli/openstack_cli.rst
@@ -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/v2.0
+ $ export OS_AUTH_URL=http://auth.example.com:5000/v3/
The command-line tool will attempt to reauthenticate using the provided
credentials for every request. You can override this behavior by manually
diff --git a/doc/source/cli/watcher.rst b/doc/source/cli/watcher.rst
index 3e7e9ac..c16066f 100644
--- a/doc/source/cli/watcher.rst
+++ b/doc/source/cli/watcher.rst
@@ -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/v2.0
+ $ export OS_AUTH_URL=http://auth.example.com:5000/v3/
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://git.openstack.org/cgit/openstack/python-watcherclient/tree/tools/watcher.bash_completion)
+https://opendev.org/openstack/python-watcherclient/src/branch/master/tools/watcher.bash_completion)
to your terminal and then bash completion should work::
$ . watcher.bash_completion
diff --git a/doc/source/contributing.rst b/doc/source/contributing.rst
index a9a16a4..b8b62db 100644
--- a/doc/source/contributing.rst
+++ b/doc/source/contributing.rst
@@ -44,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-dev
+ http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-discuss
Code Hosting
- https://git.openstack.org/cgit/openstack/python-watcherclient
+ https://opendev.org/openstack/python-watcherclient
Code Review
- https://review.openstack.org/#/q/status:open+project:openstack/python-watcherclient,n,z
+ https://review.opendev.org/#/q/status:open+project:openstack/python-watcherclient,n,z
diff --git a/doc/source/installation.rst b/doc/source/installation.rst
index a8f125f..5b29553 100644
--- a/doc/source/installation.rst
+++ b/doc/source/installation.rst
@@ -5,6 +5,6 @@ Installation
If you have `virtualenvwrapper <https://virtualenvwrapper.readthedocs.org/en/latest/install.html>`_ installed::
$ mkvirtualenv python-watcherclient
- $ git clone https://git.openstack.org/openstack/python-watcherclient
+ $ git clone https://opendev.org/openstack/python-watcherclient
$ cd python-watcherclient && python setup.py install
$ pip install -r ./requirements.txt
diff --git a/lower-constraints.txt b/lower-constraints.txt
index 4ce6e94..10d5e60 100644
--- a/lower-constraints.txt
+++ b/lower-constraints.txt
@@ -58,7 +58,6 @@ 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
diff --git a/requirements.txt b/requirements.txt
index 1eb6119..2db0308 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -6,9 +6,9 @@ Babel!=2.4.0,>=2.5.3 # BSD
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
-PrettyTable<0.8,>=0.7.2 # BSD
keystoneauth1>=3.4.0 # Apache-2.0
six>=1.11.0 # MIT
PyYAML>=3.12 # MIT
diff --git a/setup.cfg b/setup.cfg
index 33046b9..94e9f1c 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -4,7 +4,7 @@ summary = Python client library for Watcher API
description-file =
README.rst
author = OpenStack
-author-email = openstack-dev@lists.openstack.org
+author-email = openstack-discuss@lists.openstack.org
home-page = https://docs.openstack.org/python-watcherclient/latest/
classifier =
Environment :: OpenStack
@@ -16,7 +16,8 @@ classifier =
Programming Language :: Python :: 2
Programming Language :: Python :: 2.7
Programming Language :: Python :: 3
- Programming Language :: Python :: 3.5
+ Programming Language :: Python :: 3.6
+ Programming Language :: Python :: 3.7
[files]
packages =
@@ -25,8 +26,6 @@ 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
@@ -53,6 +52,7 @@ 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
@@ -67,6 +67,8 @@ 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
@@ -104,6 +106,8 @@ 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 =
diff --git a/test-requirements.txt b/test-requirements.txt
index a13815b..db4dace 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -4,12 +4,10 @@
coverage!=4.4,>=4.0 # Apache-2.0
fixtures>=3.0.0 # Apache-2.0/BSD
-hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0
+hacking>=1.1.0,<1.2.0 # Apache-2.0
mock>=2.0.0 # BSD
-openstackdocstheme>=1.18.1 # Apache-2.0
oslotest>=3.2.0 # Apache-2.0
python-subunit>=1.0.0 # Apache-2.0/BSD
-sphinx!=1.6.6,!=1.6.7,>=1.6.5 # BSD
stestr>=2.0.0 # Apache-2.0
testscenarios>=0.4 # Apache-2.0/BSD
testtools>=2.2.0 # MIT
diff --git a/tox.ini b/tox.ini
index 9c9387b..a21f2aa 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,18 +1,24 @@
[tox]
-minversion = 1.8
-envlist = py35,py27,pep8
+minversion = 2.0
+envlist = py36,py37,py27,pep8
skipsdist = True
[testenv]
usedevelop = True
-install_command =
- constraints: pip install -U --force-reinstall -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages}
- pip install -U {opts} {packages}
+passenv = ZUUL_CACHE_DIR
+ REQUIREMENTS_PIP_LOCATION
+install_command = pip install {opts} {packages}
setenv =
VIRTUAL_ENV={envdir}
-deps = -r{toxinidir}/test-requirements.txt
+deps =
+ -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}
+ -r{toxinidir}/test-requirements.txt
+ -r{toxinidir}/requirements.txt
+whitelist_externals =
+ rm
commands = rm -f .testrepository/times.dbm
- stestr --test-path=./watcherclient/tests/unit run {posargs}
+ # The --test-path is defined in .stestr.conf
+ stestr run {posargs}
stestr slowest
[testenv:pep8]
@@ -36,26 +42,16 @@ commands =
[testenv:docs]
basepython = python3
+deps =
+ -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}
+ -r{toxinidir}/requirements.txt
+ -r{toxinidir}/doc/requirements.txt
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
-commands = stestr --test-path=./watcherclient/tests/functional run --concurrency=1 {posargs}
- stestr slowest
-
[flake8]
# E123, E125 skipped as they are invalid PEP-8.
show-source = True
diff --git a/watcherclient/__init__.py b/watcherclient/__init__.py
index aa107bb..5492c60 100644
--- a/watcherclient/__init__.py
+++ b/watcherclient/__init__.py
@@ -17,6 +17,7 @@
import pbr.version
from watcherclient import client
+from watcherclient.common import api_versioning
from watcherclient import exceptions
@@ -24,3 +25,11 @@ __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.1")
diff --git a/watcherclient/client.py b/watcherclient/client.py
index 6627ff4..0494543 100644
--- a/watcherclient/client.py
+++ b/watcherclient/client.py
@@ -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_watcher_api_version=None, max_retries=None,
+ os_infra_optim_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_watcher_api_version: watcher API version to use
+ :param os_infra_optim_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_watcher_api_version': os_watcher_api_version,
+ 'os_infra_optim_api_version': os_infra_optim_api_version,
'max_retries': max_retries,
'retry_interval': retry_interval,
}
@@ -182,4 +182,11 @@ 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)
diff --git a/watcherclient/common/api_versioning.py b/watcherclient/common/api_versioning.py
index 40c546c..9edcff9 100644
--- a/watcherclient/common/api_versioning.py
+++ b/watcherclient/common/api_versioning.py
@@ -26,14 +26,34 @@ if not LOG.handlers:
LOG.addHandler(logging.StreamHandler())
+MINOR_1_START_END_TIMING = '1.1'
+MINOR_2_FORCE_AUDIT = '1.2'
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))
+
+
class APIVersion(object):
"""This class represents an API Version Request.
diff --git a/watcherclient/common/base.py b/watcherclient/common/base.py
index 05a9093..fc33b45 100644
--- a/watcherclient/common/base.py
+++ b/watcherclient/common/base.py
@@ -31,10 +31,7 @@ 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.
"""
- try:
- return obj.id
- except AttributeError:
- return obj
+ return getattr(obj, 'id', obj)
class Manager(object):
diff --git a/watcherclient/common/cliutils.py b/watcherclient/common/cliutils.py
deleted file mode 100644
index cc24e66..0000000
--- a/watcherclient/common/cliutils.py
+++ /dev/null
@@ -1,278 +0,0 @@
-# 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)
diff --git a/watcherclient/common/httpclient.py b/watcherclient/common/httpclient.py
index 36a8469..f0cd592 100644
--- a/watcherclient/common/httpclient.py
+++ b/watcherclient/common/httpclient.py
@@ -34,16 +34,15 @@ 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
-# 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'
-
+# Record the latest version that this client was tested with.
+DEFAULT_VER = '1.latest'
+# Minor version 2 for adding force option to audit
+LAST_KNOWN_API_VERSION = 2
+LATEST_VERSION = '1.{}'.format(LAST_KNOWN_API_VERSION)
LOG = logging.getLogger(__name__)
USER_AGENT = 'python-watcherclient'
@@ -104,20 +103,8 @@ 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':
@@ -126,33 +113,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_watcher_api_version,
+ % {'req': self.os_infra_optim_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_watcher_api_version,
+ % {'req': self.os_infra_optim_api_version,
'min': min_ver, 'max': max_ver}))
negotiated_ver = str(
- min(version.StrictVersion(self.os_watcher_api_version),
+ min(version.StrictVersion(self.os_infra_optim_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_watcher_api_version = negotiated_ver
+ self.os_infra_optim_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('X-OpenStack-Watcher-API-Minimum-Version',
+ min_ver = accessor_func('OpenStack-API-Minimum-Version',
None)
- max_ver = accessor_func('X-OpenStack-Watcher-API-Maximum-Version',
+ max_ver = accessor_func('OpenStack-API-Maximum-Version',
None)
return min_ver, max_ver
@@ -206,8 +193,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_watcher_api_version = kwargs.get('os_watcher_api_version',
- DEFAULT_VER)
+ self.os_infra_optim_api_version = kwargs.get(
+ 'os_infra_optim_api_version', DEFAULT_VER)
self.api_version_select_state = kwargs.get(
'api_version_select_state', 'default')
self.conflict_max_retries = kwargs.pop('max_retries',
@@ -289,7 +276,7 @@ class HTTPClient(VersionNegotiationMixin):
LOG.debug('\n'.join(dump))
def _make_connection_url(self, url):
- return urlparse.urljoin(self.endpoint_trimmed, url)
+ return '%s/%s' % (self.endpoint_trimmed.rstrip('/'), url.lstrip('/'))
def _parse_version_headers(self, resp):
return self._generic_parse_version_headers(resp.headers.get)
@@ -307,9 +294,15 @@ 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_watcher_api_version:
- kwargs['headers'].setdefault('X-OpenStack-Watcher-API-Version',
- self.os_watcher_api_version)
+ 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.auth_token:
kwargs['headers'].setdefault('X-Auth-Token', self.auth_token)
@@ -334,8 +327,8 @@ class HTTPClient(VersionNegotiationMixin):
if resp.status_code == http_client.NOT_ACCEPTABLE:
negotiated_ver = self.negotiate_version(self.session, resp)
- kwargs['headers']['X-OpenStack-Watcher-API-Version'] = (
- negotiated_ver)
+ kwargs['headers']['OpenStack-API-Version'] = (
+ ' '.join(['infra-optim', negotiated_ver]))
return self._http_request(url, method, **kwargs)
except requests.exceptions.RequestException as e:
@@ -395,8 +388,8 @@ class HTTPClient(VersionNegotiationMixin):
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):
+ http_client.RESET_CONTENT) or
+ content_type is None):
return resp, list()
if 'application/json' in content_type:
@@ -485,13 +478,13 @@ class SessionClient(VersionNegotiationMixin, adapter.LegacyJsonAdapter):
"""HTTP client based on Keystone client session."""
def __init__(self,
- os_watcher_api_version,
+ os_infra_optim_api_version,
api_version_select_state,
max_retries,
retry_interval,
endpoint,
**kwargs):
- self.os_watcher_api_version = os_watcher_api_version
+ self.os_infra_optim_api_version = os_infra_optim_api_version
self.api_version_select_state = api_version_select_state
self.conflict_max_retries = max_retries
self.conflict_retry_interval = retry_interval
@@ -516,9 +509,16 @@ class SessionClient(VersionNegotiationMixin, adapter.LegacyJsonAdapter):
_trim_endpoint_api_version(self.endpoint_override)
)
- if getattr(self, 'os_watcher_api_version', None):
- kwargs['headers'].setdefault('X-OpenStack-Watcher-API-Version',
- self.os_watcher_api_version)
+ 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()]))
endpoint_filter = kwargs.setdefault('endpoint_filter', {})
endpoint_filter.setdefault('interface', self.interface)
@@ -529,8 +529,8 @@ class SessionClient(VersionNegotiationMixin, adapter.LegacyJsonAdapter):
raise_exc=False, **kwargs)
if resp.status_code == http_client.NOT_ACCEPTABLE:
negotiated_ver = self.negotiate_version(self.session, resp)
- kwargs['headers']['X-OpenStack-Watcher-API-Version'] = (
- negotiated_ver)
+ kwargs['headers']['OpenStack-API-Version'] = (
+ ' '.join(['infra-optim', negotiated_ver]))
return self._http_request(url, method, **kwargs)
if resp.status_code >= http_client.BAD_REQUEST:
error_json = _extract_error_json(resp.content)
@@ -582,7 +582,7 @@ def _construct_http_client(endpoint=None,
session=None,
token=None,
auth_ref=None,
- os_watcher_api_version=DEFAULT_VER,
+ os_infra_optim_api_version=DEFAULT_VER,
api_version_select_state='default',
max_retries=DEFAULT_MAX_RETRIES,
retry_interval=DEFAULT_RETRY_INTERVAL,
@@ -613,27 +613,29 @@ def _construct_http_client(endpoint=None,
'the session to construct a client: %s',
', '.join(dvars))
- 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)
+ 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)
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_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)
+ 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)
diff --git a/watcherclient/config.py b/watcherclient/config.py
deleted file mode 100644
index 892d34e..0000000
--- a/watcherclient/config.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# -*- 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")
diff --git a/watcherclient/osc/plugin.py b/watcherclient/osc/plugin.py
index 5885de3..df4bc23 100644
--- a/watcherclient/osc/plugin.py
+++ b/watcherclient/osc/plugin.py
@@ -15,9 +15,14 @@ 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 = '1'
+DEFAULT_API_VERSION = httpclient.LATEST_VERSION
API_VERSION_OPTION = 'os_infra_optim_api_version'
API_NAME = 'infra-optim'
API_VERSIONS = {
@@ -27,14 +32,17 @@ 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,
- instance._api_version[API_NAME],
+ version.ver_major,
API_VERSIONS)
LOG.debug('Instantiating infraoptim client: %s', infraoptim_client_class)
client = infraoptim_client_class(
- os_watcher_api_version=instance._api_version[API_NAME],
+ os_infra_optim_api_version=instance._api_version[API_NAME],
session=instance.session,
region_name=instance._region_name,
)
@@ -53,3 +61,30 @@ 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
diff --git a/watcherclient/plugin.py b/watcherclient/plugin.py
deleted file mode 100644
index a8f78e4..0000000
--- a/watcherclient/plugin.py
+++ /dev/null
@@ -1,34 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-
-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])]
diff --git a/watcherclient/shell.py b/watcherclient/shell.py
index f8f9a46..4f2e113 100644
--- a/watcherclient/shell.py
+++ b/watcherclient/shell.py
@@ -39,6 +39,7 @@ 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']
@@ -147,11 +148,13 @@ class WatcherShell(app.App):
metavar='<auth-token>',
default=utils.env('OS_AUTH_TOKEN'),
help='Defaults to env[OS_AUTH_TOKEN].')
- 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-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-endpoint-type',
default=utils.env('OS_ENDPOINT_TYPE'),
help='Defaults to env[OS_ENDPOINT_TYPE] or '
@@ -204,5 +207,6 @@ 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:]))
diff --git a/watcherclient/tests/README.rst b/watcherclient/tests/README.rst
deleted file mode 100644
index 7600e1f..0000000
--- a/watcherclient/tests/README.rst
+++ /dev/null
@@ -1,61 +0,0 @@
-..
- 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
diff --git a/watcherclient/tests/functional/__init__.py b/watcherclient/tests/functional/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/watcherclient/tests/functional/__init__.py
+++ /dev/null
diff --git a/watcherclient/tests/functional/v1/__init__.py b/watcherclient/tests/functional/v1/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/watcherclient/tests/functional/v1/__init__.py
+++ /dev/null
diff --git a/watcherclient/tests/functional/v1/base.py b/watcherclient/tests/functional/v1/base.py
deleted file mode 100644
index 229a16d..0000000
--- a/watcherclient/tests/functional/v1/base.py
+++ /dev/null
@@ -1,144 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-import os
-
-import re
-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 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', 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=False):
- """Executes specified command for the given action."""
- cmdlist = shlex.split(cmd)
- cmdlist.extend(credentials())
- stdout = subprocess.PIPE
- stderr = subprocess.STDOUT if merge_stderr else subprocess.PIPE
- proc = subprocess.Popen(cmdlist, stdout=stdout, stderr=stderr)
- result, result_err = proc.communicate()
- result = result.decode('utf-8')
- if not fail_ok and proc.returncode != 0:
- raise exceptions.CommandFailed(proc.returncode, cmd, result,
- result_err)
- return result
-
-
-class TestCase(testtools.TestCase):
-
- delimiter_line = re.compile('^\+\-[\+\-]+\-\+$')
-
- @classmethod
- def watcher(cls, cmd, fail_ok=False):
- """Executes watcherclient command for the given action."""
- return execute('watcher {0}'.format(cmd), fail_ok=fail_ok)
-
- @classmethod
- def get_opts(cls, fields, format='value'):
- return ' -f {0} {1}'.format(format,
- ' '.join(['-c ' + it for it in fields]))
-
- @classmethod
- def assertOutput(cls, expected, actual):
- if expected != actual:
- raise Exception('{0} != {1}'.format(expected, actual))
-
- @classmethod
- def assertInOutput(cls, expected, actual):
- if expected not in actual:
- raise Exception('{0} not in {1}'.format(expected, actual))
-
- def assert_table_structure(self, items, field_names):
- """Verify that all items have keys listed in field_names."""
- for item in items:
- for field in field_names:
- self.assertIn(field, item)
-
- 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 six.iterkeys(item):
- self.assertIn(key, field_names)
-
- def assert_show_structure(self, items, field_names):
- """Verify that all field_names listed in keys of all items."""
- if isinstance(items, list):
- o = {}
- for d in items:
- o.update(d)
- else:
- o = items
- item_keys = o.keys()
- for field in field_names:
- self.assertIn(field, item_keys)
-
- @staticmethod
- def parse_show_as_object(raw_output):
- """Return a dict with values parsed from cli output."""
- items = TestCase.parse_show(raw_output)
- o = {}
- for item in items:
- o.update(item)
- return o
-
- @staticmethod
- def parse_show(raw_output):
- """Return list of dicts with item values parsed from cli output."""
-
- items = []
- table_ = output_parser.table(raw_output)
- for row in table_['values']:
- item = {}
- item[row[0]] = row[1]
- items.append(item)
- return items
-
- def parse_listing(self, raw_output):
- """Return list of dicts with basic item parsed from cli output."""
- return output_parser.listing(raw_output)
-
- def has_actionplan_succeeded(self, ap_uuid):
- return self.parse_show_as_object(
- self.watcher('actionplan show %s' % ap_uuid)
- )['State'] == 'SUCCEEDED'
-
- @classmethod
- def has_audit_created(cls, audit_uuid):
- return cls.parse_show_as_object(
- cls.watcher('audit show %s' % audit_uuid))['State'] == 'SUCCEEDED'
diff --git a/watcherclient/tests/functional/v1/test_action.py b/watcherclient/tests/functional/v1/test_action.py
deleted file mode 100644
index 03f0ca0..0000000
--- a/watcherclient/tests/functional/v1/test_action.py
+++ /dev/null
@@ -1,81 +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.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)
diff --git a/watcherclient/tests/functional/v1/test_action_plan.py b/watcherclient/tests/functional/v1/test_action_plan.py
deleted file mode 100644
index 3cd3340..0000000
--- a/watcherclient/tests/functional/v1/test_action_plan.py
+++ /dev/null
@@ -1,97 +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.functional.v1 import base
-
-
-class ActionPlanTests(base.TestCase):
- """Functional tests for action plan."""
-
- dummy_name = 'dummy'
- list_fields = ['UUID', 'Audit', 'State', 'Updated At', 'Global efficacy']
- detailed_list_fields = list_fields + ['Created At', 'Deleted At',
- 'Strategy', 'Efficacy indicators',
- 'Hostname']
- 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_raw_output = cls.watcher('audit create -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')
-
- @classmethod
- def tearDownClass(cls):
- # Delete action plan
- 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_plan_list(self):
- raw_output = self.watcher('actionplan list')
- self.assert_table_structure([raw_output], self.list_fields)
-
- def test_action_plan_detailed_list(self):
- raw_output = self.watcher('actionplan list --detail')
- self.assert_table_structure([raw_output], self.detailed_list_fields)
-
- def test_action_plan_show(self):
- action_plan_list = self.parse_show(self.watcher('actionplan list'))
- action_plan_uuid = list(action_plan_list[0])[0]
- actionplan = self.watcher('actionplan show %s' % action_plan_uuid)
- self.assertIn(action_plan_uuid, actionplan)
- self.assert_table_structure([actionplan],
- self.detailed_list_fields)
-
- def test_action_plan_start(self):
- output = self.parse_show(self.watcher('actionplan list --audit %s'
- % self.audit_uuid))
- action_plan_uuid = list(output[0])[0]
- self.watcher('actionplan start %s' % action_plan_uuid)
- raw_output = self.watcher('actionplan show %s' % action_plan_uuid)
- self.assert_table_structure([raw_output], self.detailed_list_fields)
-
- self.assertTrue(test_utils.call_until_true(
- func=functools.partial(
- self.has_actionplan_succeeded, action_plan_uuid),
- duration=600,
- sleep_for=2
- ))
diff --git a/watcherclient/tests/functional/v1/test_audit.py b/watcherclient/tests/functional/v1/test_audit.py
deleted file mode 100644
index ee74da7..0000000
--- a/watcherclient/tests/functional/v1/test_audit.py
+++ /dev/null
@@ -1,136 +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.functional.v1 import base
-
-
-class AuditTests(base.TestCase):
- """Functional tests for audit."""
-
- dummy_name = 'dummy'
- list_fields = ['UUID', 'Name', 'Audit Type', 'State', 'Goal', 'Strategy']
- detailed_list_fields = list_fields + ['Created At', 'Updated At',
- 'Deleted At', 'Parameters',
- 'Interval', 'Audit Scope',
- 'Next Run Time', 'Hostname']
- audit_template_name = 'a' + uuidutils.generate_uuid()
- audit_uuid = None
-
- @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 -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')
-
- @classmethod
- def tearDownClass(cls):
- output = cls.parse_show(
- cls.watcher('actionplan list --audit %s' % cls.audit_uuid))
- action_plan_uuid = list(output[0])[0]
- cls.watcher('actionplan delete %s' % action_plan_uuid)
- cls.watcher('audit delete %s' % cls.audit_uuid)
- cls.watcher('audittemplate delete %s' % cls.audit_template_name)
-
- def test_audit_list(self):
- raw_output = self.watcher('audit list')
- self.assert_table_structure([raw_output], self.list_fields)
-
- 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):
- audit_raw_output = self.watcher('audit update %s add interval=2'
- % self.audit_uuid)
- audit_output = self.parse_show_as_object(audit_raw_output)
- assert int(audit_output['Interval']) == 2
-
-
-class AuditActiveTests(base.TestCase):
-
- list_fields = ['UUID', 'Name', 'Audit Type', 'State', 'Goal', 'Strategy']
- detailed_list_fields = list_fields + ['Created At', 'Updated At',
- 'Deleted At', 'Parameters',
- 'Interval', 'Audit Scope']
- audit_template_name = 'a' + uuidutils.generate_uuid()
-
- @classmethod
- def setUpClass(cls):
- cls.watcher('audittemplate create %s dummy -s dummy'
- % cls.audit_template_name)
-
- @classmethod
- def tearDownClass(cls):
- cls.watcher('audittemplate delete %s' % cls.audit_template_name)
-
- def _create_audit(self):
- return self.parse_show_as_object(
- self.watcher('audit create -a %s'
- % self.audit_template_name))['UUID']
-
- def _delete_audit(self, audit_uuid):
- self.assertTrue(test_utils.call_until_true(
- func=functools.partial(
- self.has_audit_created, audit_uuid),
- duration=600,
- sleep_for=2
- ))
- output = self.parse_show(
- self.watcher('actionplan list --audit %s' % audit_uuid))
- action_plan_uuid = list(output[0])[0]
- self.watcher('actionplan delete %s' % action_plan_uuid)
- self.watcher('audit delete %s' % audit_uuid)
-
- 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_audit(self):
- audit_uuid = self._create_audit()
- self.assertTrue(test_utils.call_until_true(
- func=functools.partial(
- self.has_audit_created, audit_uuid),
- duration=600,
- sleep_for=2
- ))
- raw_output = self.watcher('audit delete %s' % audit_uuid)
- self.assertOutput('', raw_output)
- output = self.parse_show(
- self.watcher('actionplan list --audit %s' % audit_uuid))
- action_plan_uuid = list(output[0])[0]
- self.watcher('actionplan delete %s' % action_plan_uuid)
diff --git a/watcherclient/tests/functional/v1/test_audit_template.py b/watcherclient/tests/functional/v1/test_audit_template.py
deleted file mode 100644
index 7010f13..0000000
--- a/watcherclient/tests/functional/v1/test_audit_template.py
+++ /dev/null
@@ -1,89 +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
-
-from watcherclient.tests.functional.v1 import base
-
-
-class AuditTemplateTests(base.TestCase):
- """Functional tests for audit template."""
-
- dummy_name = 'dummy'
- list_fields = ['UUID', 'Name', 'Goal', 'Strategy']
- detailed_list_fields = list_fields + ['Created At', 'Updated At',
- 'Deleted At', 'Description',
- 'Audit Scope']
- audit_template_name = 'a' + uuidutils.generate_uuid()
-
- @classmethod
- def setUpClass(cls):
- cls.watcher('audittemplate create %s dummy -s dummy'
- % cls.audit_template_name)
-
- @classmethod
- def tearDownClass(cls):
- cls.watcher('audittemplate delete %s' % cls.audit_template_name)
-
- def test_audit_template_list(self):
- raw_output = self.watcher('audittemplate list')
- self.assert_table_structure([raw_output], self.list_fields)
-
- def test_audit_template_detailed_list(self):
- raw_output = self.watcher('audittemplate list --detail')
- self.assert_table_structure([raw_output], self.detailed_list_fields)
-
- def test_audit_template_show(self):
- audit_template = self.watcher(
- 'audittemplate show %s' % self.audit_template_name)
- self.assertIn(self.audit_template_name, audit_template)
- self.assert_table_structure([audit_template],
- self.detailed_list_fields)
-
- def test_audit_template_update(self):
- raw_output = self.watcher('audittemplate update %s replace '
- 'description="Updated Desc"'
- % self.audit_template_name)
- audit_template_output = self.parse_show_as_object(raw_output)
- assert audit_template_output['Description'] == 'Updated Desc'
-
-
-class AuditTemplateActiveTests(base.TestCase):
-
- audit_template_name = 'b' + uuidutils.generate_uuid()
- list_fields = ['UUID', 'Name', 'Goal', 'Strategy']
- detailed_list_fields = list_fields + ['Created At', 'Updated At',
- 'Deleted At', 'Description',
- 'Audit Scope']
-
- def _create_audit_template(self):
- self.watcher('audittemplate create %s dummy -s dummy '
- '-d "Test Audit Template"' % self.audit_template_name)
-
- def _delete_audit_template(self):
- self.watcher('audittemplate delete %s' % self.audit_template_name)
-
- def test_create_audit_template(self):
- raw_output = self.watcher('audittemplate create %s dummy '
- '-s dummy -d "Test Audit Template"'
- % self.audit_template_name)
- self.assert_table_structure([raw_output], self.detailed_list_fields)
- self._delete_audit_template()
-
- def test_delete_audit_template(self):
- self._create_audit_template()
- raw_output = self.watcher('audittemplate delete %s'
- % self.audit_template_name)
- self.assertOutput('', raw_output)
diff --git a/watcherclient/tests/functional/v1/test_goal.py b/watcherclient/tests/functional/v1/test_goal.py
deleted file mode 100644
index 31d654f..0000000
--- a/watcherclient/tests/functional/v1/test_goal.py
+++ /dev/null
@@ -1,41 +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 watcherclient.tests.functional.v1 import base
-
-
-class GoalTests(base.TestCase):
- """Functional tests for goal."""
-
- dummy_name = 'dummy'
- list_fields = ['UUID', 'Name', 'Display name']
-
- def test_goal_list(self):
- raw_output = self.watcher('goal list')
- self.assertIn(self.dummy_name, raw_output)
- self.assert_table_structure([raw_output], self.list_fields)
-
- def test_goal_detailed_list(self):
- raw_output = self.watcher('goal list --detail')
- self.assertIn(self.dummy_name, raw_output)
- self.assert_table_structure(
- [raw_output], self.list_fields + ['Efficacy specification'])
-
- def test_goal_show(self):
- raw_output = self.watcher('goal show %s' % self.dummy_name)
- self.assertIn(self.dummy_name, raw_output)
- self.assert_table_structure(
- [raw_output], self.list_fields + ['Efficacy specification'])
- self.assertNotIn('server_consolidation', raw_output)
diff --git a/watcherclient/tests/functional/v1/test_scoring_engine.py b/watcherclient/tests/functional/v1/test_scoring_engine.py
deleted file mode 100644
index 3a062b3..0000000
--- a/watcherclient/tests/functional/v1/test_scoring_engine.py
+++ /dev/null
@@ -1,40 +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 watcherclient.tests.functional.v1 import base
-
-
-class ScoringEngineTests(base.TestCase):
- """Functional tests for scoring engine."""
-
- dummy_name = 'dummy_scorer'
- list_fields = ['UUID', 'Name', 'Description']
- detailed_list_fields = list_fields + ['Metainfo']
-
- def test_scoringengine_list(self):
- raw_output = self.watcher('scoringengine list')
- self.assertIn(self.dummy_name, raw_output)
- self.assert_table_structure([raw_output], self.list_fields)
-
- def test_scoringengine_detailed_list(self):
- raw_output = self.watcher('scoringengine list --detail')
- self.assertIn(self.dummy_name, raw_output)
- self.assert_table_structure([raw_output], self.detailed_list_fields)
-
- def test_scoringengine_show(self):
- raw_output = self.watcher('scoringengine show %s' % self.dummy_name)
- self.assertIn(self.dummy_name, raw_output)
- self.assert_table_structure([raw_output], self.detailed_list_fields)
- self.assertNotIn('dummy_avg_scorer', raw_output)
diff --git a/watcherclient/tests/functional/v1/test_service.py b/watcherclient/tests/functional/v1/test_service.py
deleted file mode 100644
index d943a6b..0000000
--- a/watcherclient/tests/functional/v1/test_service.py
+++ /dev/null
@@ -1,47 +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 watcherclient.tests.functional.v1 import base
-
-
-class ServiceTests(base.TestCase):
- """Functional tests for service."""
-
- decision_engine_name = 'watcher-decision-engine'
- applier_name = 'watcher-applier'
- list_fields = ['ID', 'Name', 'Host', 'Status']
-
- def test_service_list(self):
- raw_output = self.watcher('service list')
- self.assertIn(self.decision_engine_name, raw_output)
- self.assertIn(self.applier_name, raw_output)
- self.assert_table_structure([raw_output], self.list_fields)
-
- def test_service_detailed_list(self):
- raw_output = self.watcher('service list --detail')
- self.assertIn(self.decision_engine_name, raw_output)
- self.assertIn(self.applier_name, raw_output)
- self.assert_table_structure([raw_output],
- self.list_fields + ['Last seen up'])
-
- def test_service_show(self):
- # TODO(alexchadin): this method should be refactored since Watcher will
- # get HA support soon.
- raw_output = self.watcher('service show %s'
- % self.decision_engine_name)
- self.assertIn(self.decision_engine_name, raw_output)
- self.assert_table_structure([raw_output],
- self.list_fields + ['Last seen up'])
- self.assertNotIn(self.applier_name, raw_output)
diff --git a/watcherclient/tests/functional/v1/test_strategy.py b/watcherclient/tests/functional/v1/test_strategy.py
deleted file mode 100644
index 8f87bd4..0000000
--- a/watcherclient/tests/functional/v1/test_strategy.py
+++ /dev/null
@@ -1,48 +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 watcherclient.tests.functional.v1 import base
-
-
-class StrategyTests(base.TestCase):
- """Functional tests for strategy."""
-
- dummy_name = 'dummy'
- basic_strategy = 'basic'
- list_fields = ['UUID', 'Name', 'Display name', 'Goal']
- state_fields = ['Datasource', 'Metrics', 'CDM', 'Name']
-
- def test_strategy_list(self):
- raw_output = self.watcher('strategy list')
- self.assertIn(self.dummy_name, raw_output)
- self.assert_table_structure([raw_output], self.list_fields)
-
- def test_strategy_detailed_list(self):
- raw_output = self.watcher('strategy list --detail')
- self.assertIn(self.dummy_name, raw_output)
- self.assert_table_structure([raw_output],
- self.list_fields + ['Parameters spec'])
-
- def test_strategy_show(self):
- raw_output = self.watcher('strategy show %s' % self.dummy_name)
- self.assertIn(self.dummy_name, raw_output)
- self.assert_table_structure([raw_output],
- self.list_fields + ['Parameters spec'])
- self.assertNotIn('basic', raw_output)
-
- def test_strategy_state(self):
- raw_output = self.watcher('strategy state %s' % self.basic_strategy)
- self.assertIn(self.basic_strategy, raw_output)
- self.assert_table_structure([raw_output], self.state_fields)
diff --git a/watcherclient/tests/unit/test_client.py b/watcherclient/tests/unit/test_client.py
index d788f1b..f1ce98c 100644
--- a/watcherclient/tests/unit/test_client.py
+++ b/watcherclient/tests/unit/test_client.py
@@ -118,7 +118,7 @@ class ClientTest(utils.BaseTestCase):
'os_password': 'PASSWORD',
'os_auth_url': 'http://localhost:35357/v2.0',
'os_auth_token': '',
- 'os_watcher_api_version': "latest",
+ 'os_infra_optim_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_watcher_api_version': "1.4",
+ 'os_infra_optim_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_watcher_api_version': 'latest',
+ 'os_infra_optim_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_watcher_api_version': 'latest',
+ 'os_infra_optim_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_watcher_api_version': None,
+ 'os_infra_optim_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_watcher_api_version': None,
+ 'os_infra_optim_api_version': None,
'max_retries': None,
'retry_interval': None,
'session': session,
@@ -350,3 +350,11 @@ 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)
diff --git a/watcherclient/tests/unit/v1/base.py b/watcherclient/tests/unit/v1/base.py
index e50eebf..2eed37b 100644
--- a/watcherclient/tests/unit/v1/base.py
+++ b/watcherclient/tests/unit/v1/base.py
@@ -25,7 +25,7 @@ from watcherclient.tests.unit import utils
class CommandTestCase(utils.BaseTestCase):
- def setUp(self):
+ def setUp(self, os_infra_optim_api_version='1.0'):
super(CommandTestCase, self).setUp()
self.fake_env = {
@@ -38,7 +38,7 @@ class CommandTestCase(utils.BaseTestCase):
'os_username': 'test',
'os_password': 'test',
'timeout': 600,
- 'os_watcher_api_version': '1'}
+ 'os_infra_optim_api_version': os_infra_optim_api_version}
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:
+ if formatting and formatting != 'table':
formatter_arg = " -f %s" % formatting
formatter = jsonutils.loads
else:
diff --git a/watcherclient/tests/unit/v1/test_action_plan.py b/watcherclient/tests/unit/v1/test_action_plan.py
index 94e4977..262e077 100644
--- a/watcherclient/tests/unit/v1/test_action_plan.py
+++ b/watcherclient/tests/unit/v1/test_action_plan.py
@@ -14,10 +14,13 @@
# under the License.
import copy
+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
@@ -38,9 +41,19 @@ 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':
{
@@ -129,6 +142,31 @@ 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):
@@ -215,6 +253,54 @@ 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,
diff --git a/watcherclient/tests/unit/v1/test_action_plan_shell.py b/watcherclient/tests/unit/v1/test_action_plan_shell.py
index b377641..4574355 100644
--- a/watcherclient/tests/unit/v1/test_action_plan_shell.py
+++ b/watcherclient/tests/unit/v1/test_action_plan_shell.py
@@ -17,6 +17,7 @@ import datetime
import mock
import six
+from oslo_utils.uuidutils import generate_uuid
from watcherclient import exceptions
from watcherclient import shell
from watcherclient.tests.unit.v1 import base
@@ -126,6 +127,17 @@ 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):
@@ -195,6 +207,32 @@ 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 = ''
diff --git a/watcherclient/tests/unit/v1/test_audit_shell.py b/watcherclient/tests/unit/v1/test_audit_shell.py
index a835d36..76176a7 100755
--- a/watcherclient/tests/unit/v1/test_audit_shell.py
+++ b/watcherclient/tests/unit/v1/test_audit_shell.py
@@ -20,7 +20,6 @@ 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',
@@ -52,76 +51,86 @@ 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):
- SHORT_LIST_FIELDS = resource_fields.AUDIT_SHORT_LIST_FIELDS
- SHORT_LIST_FIELD_LABELS = resource_fields.AUDIT_SHORT_LIST_FIELD_LABELS
- FIELDS = resource_fields.AUDIT_FIELDS
- FIELD_LABELS = resource_fields.AUDIT_FIELD_LABELS
-
- def setUp(self):
- super(self.__class__, self).setUp()
+ 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': '',
+ }
+
+ 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)
# goal mock
p_goal_manager = mock.patch.object(resource, 'GoalManager')
@@ -158,8 +167,8 @@ class AuditShellTest(base.CommandTestCase):
self.cmd = shell.WatcherShell(stdout=self.stdout)
def test_do_audit_list(self):
- audit1 = resource.Audit(mock.Mock(), AUDIT_1)
- audit2 = resource.Audit(mock.Mock(), AUDIT_2)
+ audit1 = resource.Audit(mock.Mock(), self.AUDIT_1)
+ audit2 = resource.Audit(mock.Mock(), self.AUDIT_2)
self.m_audit_mgr.list.return_value = [
audit1, audit2]
@@ -176,7 +185,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(), AUDIT_2)
+ audit2 = resource.Audit(mock.Mock(), self.AUDIT_2)
self.m_audit_mgr.list.return_value = [audit2]
exit_code, results = self.run_cmd(
@@ -193,8 +202,8 @@ class AuditShellTest(base.CommandTestCase):
marker='5869da81-4876-4687-a1ed-12cd64cf53d9')
def test_do_audit_list_detail(self):
- audit1 = resource.Audit(mock.Mock(), AUDIT_1)
- audit2 = resource.Audit(mock.Mock(), AUDIT_2)
+ audit1 = resource.Audit(mock.Mock(), self.AUDIT_1)
+ audit2 = resource.Audit(mock.Mock(), self.AUDIT_2)
self.m_audit_mgr.list.return_value = [
audit1, audit2]
@@ -211,7 +220,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(), AUDIT_1)
+ audit = resource.Audit(mock.Mock(), self.AUDIT_1)
self.m_audit_mgr.get.return_value = audit
exit_code, result = self.run_cmd(
@@ -225,7 +234,7 @@ class AuditShellTest(base.CommandTestCase):
'5869da81-4876-4687-a1ed-12cd64cf53d9')
def test_do_audit_show_by_name(self):
- audit = resource.Audit(mock.Mock(), AUDIT_1)
+ audit = resource.Audit(mock.Mock(), self.AUDIT_1)
self.m_audit_mgr.get.return_value = audit
exit_code, result = self.run_cmd(
@@ -278,7 +287,7 @@ class AuditShellTest(base.CommandTestCase):
'5b157edd-5a7e-4aaa-b511-f7b33ec86e9f')
def test_do_audit_update(self):
- audit = resource.Audit(mock.Mock(), AUDIT_1)
+ audit = resource.Audit(mock.Mock(), self.AUDIT_1)
self.m_audit_mgr.update.return_value = audit
exit_code, result = self.run_cmd(
@@ -294,7 +303,7 @@ class AuditShellTest(base.CommandTestCase):
[{'op': 'replace', 'path': '/state', 'value': 'PENDING'}])
def test_do_audit_update_by_name(self):
- audit = resource.Audit(mock.Mock(), AUDIT_1)
+ audit = resource.Audit(mock.Mock(), self.AUDIT_1)
self.m_audit_mgr.update.return_value = audit
exit_code, result = self.run_cmd(
@@ -309,7 +318,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(), AUDIT_3)
+ 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
@@ -328,7 +337,7 @@ class AuditShellTest(base.CommandTestCase):
)
def test_do_audit_create_with_audit_template_name(self):
- audit = resource.Audit(mock.Mock(), AUDIT_3)
+ 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
@@ -346,7 +355,7 @@ class AuditShellTest(base.CommandTestCase):
)
def test_do_audit_create_with_goal(self):
- audit = resource.Audit(mock.Mock(), AUDIT_1)
+ audit = resource.Audit(mock.Mock(), self.AUDIT_1)
self.m_audit_mgr.create.return_value = audit
exit_code, result = self.run_cmd(
@@ -363,7 +372,7 @@ class AuditShellTest(base.CommandTestCase):
)
def test_do_audit_create_with_goal_and_strategy(self):
- audit = resource.Audit(mock.Mock(), AUDIT_1)
+ audit = resource.Audit(mock.Mock(), self.AUDIT_1)
self.m_audit_mgr.create.return_value = audit
exit_code, result = self.run_cmd(
@@ -382,7 +391,7 @@ class AuditShellTest(base.CommandTestCase):
)
def test_do_audit_create_with_type(self):
- audit = resource.Audit(mock.Mock(), AUDIT_1)
+ audit = resource.Audit(mock.Mock(), self.AUDIT_1)
self.m_audit_mgr.create.return_value = audit
exit_code, result = self.run_cmd(
@@ -399,7 +408,7 @@ class AuditShellTest(base.CommandTestCase):
)
def test_do_audit_create_with_parameter(self):
- audit = resource.Audit(mock.Mock(), AUDIT_1)
+ audit = resource.Audit(mock.Mock(), self.AUDIT_1)
self.m_audit_mgr.create.return_value = audit
exit_code, result = self.run_cmd(
@@ -418,7 +427,7 @@ class AuditShellTest(base.CommandTestCase):
)
def test_do_audit_create_with_type_continuous(self):
- audit = resource.Audit(mock.Mock(), AUDIT_1)
+ audit = resource.Audit(mock.Mock(), self.AUDIT_1)
self.m_audit_mgr.create.return_value = audit
exit_code, result = self.run_cmd(
@@ -437,7 +446,7 @@ class AuditShellTest(base.CommandTestCase):
)
def test_do_audit_create_with_name(self):
- audit = resource.Audit(mock.Mock(), AUDIT_1)
+ audit = resource.Audit(mock.Mock(), self.AUDIT_1)
self.m_audit_mgr.create.return_value = audit
exit_code, result = self.run_cmd(
@@ -455,3 +464,201 @@ 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):
+ super(AuditShellTestv12, self).setUp(os_infra_optim_api_version='1.2')
+ 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_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
+ )
diff --git a/watcherclient/tests/unit/v1/test_data_model.py b/watcherclient/tests/unit/v1/test_data_model.py
new file mode 100644
index 0000000..0991fc4
--- /dev/null
+++ b/watcherclient/tests/unit/v1/test_data_model.py
@@ -0,0 +1,75 @@
+# 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))
diff --git a/watcherclient/tests/unit/v1/test_data_model_shell.py b/watcherclient/tests/unit/v1/test_data_model_shell.py
new file mode 100644
index 0000000..ad8a8c6
--- /dev/null
+++ b/watcherclient/tests/unit/v1/test_data_model_shell.py
@@ -0,0 +1,129 @@
+# 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 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
+
+
+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 = six.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)
diff --git a/watcherclient/tests/unit/v1/test_goal.py b/watcherclient/tests/unit/v1/test_goal.py
index 7f4fd3a..65ef26e 100644
--- a/watcherclient/tests/unit/v1/test_goal.py
+++ b/watcherclient/tests/unit/v1/test_goal.py
@@ -98,6 +98,16 @@ fake_responses_sorting = {
},
}
+fake_responses_marker = {
+ '/v1/goals/?marker=fc087747-61be-4aad-8126-b701731ae836':
+ {
+ 'GET': (
+ {},
+ {"goals": [GOAL2]}
+ ),
+ },
+}
+
class GoalManagerTest(testtools.TestCase):
@@ -132,6 +142,17 @@ 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)
diff --git a/watcherclient/tests/unit/v1/test_goal_shell.py b/watcherclient/tests/unit/v1/test_goal_shell.py
index 1e9ffd8..363ee56 100644
--- a/watcherclient/tests/unit/v1/test_goal_shell.py
+++ b/watcherclient/tests/unit/v1/test_goal_shell.py
@@ -93,6 +93,23 @@ 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)
diff --git a/watcherclient/tests/unit/v1/test_strategy.py b/watcherclient/tests/unit/v1/test_strategy.py
index d6f332e..59cd90d 100644
--- a/watcherclient/tests/unit/v1/test_strategy.py
+++ b/watcherclient/tests/unit/v1/test_strategy.py
@@ -107,6 +107,16 @@ fake_responses_sorting = {
},
}
+fake_responses_marker = {
+ '/v1/strategies/?marker=2cf86250-d309-4b81-818e-1537f3dba6e5':
+ {
+ 'GET': (
+ {},
+ {"strategies": [STRATEGY2]}
+ ),
+ },
+}
+
class StrategyManagerTest(testtools.TestCase):
@@ -131,6 +141,19 @@ 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)
diff --git a/watcherclient/tests/unit/v1/test_strategy_shell.py b/watcherclient/tests/unit/v1/test_strategy_shell.py
index 2834fe1..7a15687 100644
--- a/watcherclient/tests/unit/v1/test_strategy_shell.py
+++ b/watcherclient/tests/unit/v1/test_strategy_shell.py
@@ -90,6 +90,23 @@ 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)
diff --git a/watcherclient/v1/__init__.py b/watcherclient/v1/__init__.py
index 075f37c..d86d683 100644
--- a/watcherclient/v1/__init__.py
+++ b/watcherclient/v1/__init__.py
@@ -17,6 +17,7 @@ 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
@@ -38,9 +39,12 @@ 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")
+ "Service", "ServiceManager", "Strategy", "StrategyManager",
+ "DataModel", "DataModelManager")
diff --git a/watcherclient/v1/audit.py b/watcherclient/v1/audit.py
index d2b9098..803342c 100644
--- a/watcherclient/v1/audit.py
+++ b/watcherclient/v1/audit.py
@@ -20,7 +20,7 @@ from watcherclient import exceptions as exc
CREATION_ATTRIBUTES = ['audit_template_uuid', 'audit_type', 'interval',
'parameters', 'goal', 'strategy', 'auto_trigger',
- 'name']
+ 'name', 'start_time', 'end_time', 'force']
class Audit(base.Resource):
diff --git a/watcherclient/v1/audit_shell.py b/watcherclient/v1/audit_shell.py
index 4da64a9..20e7f62 100644
--- a/watcherclient/v1/audit_shell.py
+++ b/watcherclient/v1/audit_shell.py
@@ -13,16 +13,34 @@
# 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')
+ return fields, field_labels
+
+
class ShowAudit(command.ShowOne):
"""Show detailed information about a given audit."""
@@ -47,6 +65,8 @@ 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)
@@ -118,6 +138,10 @@ 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))
@@ -187,6 +211,24 @@ 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
@@ -194,7 +236,18 @@ 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']
+ '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')
fields = dict((k, v) for (k, v) in vars(parsed_args).items()
if k in field_list and v is not None)
@@ -211,6 +264,8 @@ 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)
@@ -252,6 +307,9 @@ 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)
diff --git a/watcherclient/v1/client.py b/watcherclient/v1/client.py
index abdb57c..c76ecf1 100644
--- a/watcherclient/v1/client.py
+++ b/watcherclient/v1/client.py
@@ -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_watcher_api_version'):
+ if kwargs.get('os_infra_optim_api_version'):
kwargs['api_version_select_state'] = "user"
else:
if not endpoint:
raise exceptions.EndpointException(
- _("Must provide 'endpoint' if os_watcher_api_version "
+ _("Must provide 'endpoint' if os_infra_optim_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_watcher_api_version'] = httpclient.DEFAULT_VER
+ kwargs['os_infra_optim_api_version'] = httpclient.DEFAULT_VER
self.http_client = httpclient._construct_http_client(
endpoint, *args, **kwargs)
@@ -56,3 +56,4 @@ 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)
diff --git a/watcherclient/v1/data_model.py b/watcherclient/v1/data_model.py
new file mode 100644
index 0000000..c44e5b2
--- /dev/null
+++ b/watcherclient/v1/data_model.py
@@ -0,0 +1,56 @@
+# 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]
diff --git a/watcherclient/v1/data_model_shell.py b/watcherclient/v1/data_model_shell.py
new file mode 100644
index 0000000..21d82ba
--- /dev/null
+++ b/watcherclient/v1/data_model_shell.py
@@ -0,0 +1,77 @@
+# 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))
diff --git a/watcherclient/v1/goal.py b/watcherclient/v1/goal.py
index 485a464..994812d 100644
--- a/watcherclient/v1/goal.py
+++ b/watcherclient/v1/goal.py
@@ -29,7 +29,8 @@ 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):
+ def list(self, limit=None, sort_key=None, sort_dir=None, detail=False,
+ marker=None):
"""Retrieve a list of goal.
:param limit: The maximum number of results to return per
@@ -49,13 +50,15 @@ class GoalManager(base.Manager):
:param detail: Optional, boolean whether to return detailed information
about audits.
- :returns: A list of audits.
+ :param marker: Optional, UUID of the last goal in the previous page.
+
+ :returns: A list of goals.
"""
if limit is not None:
limit = int(limit)
- filters = utils.common_filters(limit, sort_key, sort_dir)
+ filters = utils.common_filters(limit, sort_key, sort_dir, marker)
path = ''
if detail:
path += 'detail'
diff --git a/watcherclient/v1/goal_shell.py b/watcherclient/v1/goal_shell.py
index 351e42d..5852975 100644
--- a/watcherclient/v1/goal_shell.py
+++ b/watcherclient/v1/goal_shell.py
@@ -96,6 +96,13 @@ 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
diff --git a/watcherclient/v1/resource_fields.py b/watcherclient/v1/resource_fields.py
index 5682bf5..2bfef2e 100755
--- a/watcherclient/v1/resource_fields.py
+++ b/watcherclient/v1/resource_fields.py
@@ -33,12 +33,13 @@ 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']
+ 'hostname', 'start_time', 'end_time', 'force']
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']
+ 'Next Run Time', 'Hostname', 'Start Time', 'End Time',
+ 'Force']
AUDIT_SHORT_LIST_FIELDS = ['uuid', 'name', 'audit_type',
'state', 'goal_name', 'strategy_name',
@@ -97,6 +98,28 @@ 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']
diff --git a/watcherclient/v1/strategy.py b/watcherclient/v1/strategy.py
index c464513..0d91b07 100644
--- a/watcherclient/v1/strategy.py
+++ b/watcherclient/v1/strategy.py
@@ -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):
+ sort_dir=None, detail=False, marker=None):
"""Retrieve a list of strategy.
:param goal: The UUID of the goal to filter by
@@ -58,14 +58,15 @@ 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)
+ filters = utils.common_filters(limit, sort_key, sort_dir, marker)
if goal:
filters.append(parse.urlencode(dict(goal=goal)))
diff --git a/watcherclient/v1/strategy_shell.py b/watcherclient/v1/strategy_shell.py
index 7c0161b..88fd1d8 100644
--- a/watcherclient/v1/strategy_shell.py
+++ b/watcherclient/v1/strategy_shell.py
@@ -123,7 +123,13 @@ 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):