diff options
| author | Thomas Goirand <zigo@debian.org> | 2019-09-17 10:06:03 +0200 |
|---|---|---|
| committer | git-ubuntu importer <ubuntu-devel-discuss@lists.ubuntu.com> | 2019-09-17 16:28:27 +0000 |
| commit | 54fa99c6d9cd3ce69b879088cdef1da68698ded8 (patch) | |
| tree | 8620057aca06a8640ea3b45f7428bd65b823a7d7 | |
| parent | fd1fc7c96d10e5f26e5f1b7b0485b486e6218acd (diff) | |
| parent | 4325b25e160b4577395b85c73177899a153f29ca (diff) | |
2.4.0-1 (patches applied)applied/2.4.0-1
Imported using git-ubuntu import.
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 @@ -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 @@ -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=./ @@ -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 @@ -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 @@ -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 @@ -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): |
