Compare commits

..

3 Commits

Author SHA1 Message Date
1f119f46ef Update .gitreview for unmaintained/xena
Change-Id: I8a705ceee193034f0b6d3e22a62dcba57fa8c63d
2024-03-05 20:13:27 +00:00
1fc5643008 Update TOX_CONSTRAINTS_FILE for stable/xena
Update the URL to the upper-constraints file to point to the redirect
rule on releases.openstack.org so that anyone working on this branch
will switch to the correct upper-constraints list automatically when
the requirements repository branches.

Until the requirements repository has as stable/xena branch, tests will
continue to use the upper-constraints list on master.

Change-Id: I76573020a2a9ed503b1a35faff3e37a5b0561cd0
2021-09-10 14:32:21 +00:00
7cf18b413b Update .gitreview for stable/xena
Change-Id: I55f902f8ef96673ef7331964c086b9fddaabcbc0
2021-09-10 14:32:19 +00:00
53 changed files with 219 additions and 1439 deletions

View File

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

View File

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

View File

@@ -24,7 +24,7 @@ The watcher client is the command-line interface (CLI) for the
Infrastructure Optimization service (watcher) API
and its extensions.
This chapter documents watcherclient version ``4.9.0``.
This chapter documents :command:`watcher` version ``1.3.0``.
For help on a specific :command:`watcher` command, enter:
@@ -214,37 +214,6 @@ Show detailed information about a given action.
``-h, --help``
show this help message and exit
.. _watcher_action_update:
watcher action update
---------------------
.. code-block:: console
usage: watcher action update [-h] [-f {html,json,shell,table,value,yaml}]
[-c COLUMN] [--max-width <integer>] [--fit-width]
[--print-empty] [--noindent] [--prefix PREFIX]
[--state <state>] [--reason <reason>] <action>
Update action command.
**Positional arguments:**
``<action>``
UUID of the action
**Optional arguments:**
``-h, --help``
show this help message and exit
``--state <state>``
New state for the action (e.g., SKIPPED)
``--reason <reason>``
Reason for the action state change.
.. _watcher_actionplan_cancel:
watcher actionplan cancel

View File

@@ -8,24 +8,19 @@ If you're interested in contributing to the python-watcherclient project,
the following will help get you started.
Developer Certificate of Origin
-------------------------------
Contributor License Agreement
-----------------------------
.. index::
single: license; agreement
In order to contribute to the python-watcherclient project, you need to adhere
to the `Developer Certificate of Origin`_. OpenStack utilizes the Developer
Certificate of Origin (DCO) as a lightweight means to confirm that you are
entitled to contribute the code you submit. This ensures that you are
providing your contributions under the project's license and that you have
the right to do so.
.. _Developer Certificate of Origin: https://developercertificate.org/
In order to contribute to the python-watcherclient project, you need to have
signed OpenStack's contributor's agreement.
.. seealso::
* https://docs.openstack.org/contributors/common/dco.html
* https://docs.openstack.org/infra/manual/developers.html
* https://wiki.openstack.org/CLA
LaunchPad Project
-----------------

82
lower-constraints.txt Normal file
View File

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

View File

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

View File

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

View File

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

View File

@@ -1,3 +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.
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
@@ -5,4 +9,5 @@ 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
keystoneauth1>=3.4.0 # Apache-2.0
six>=1.11.0 # MIT
PyYAML>=3.13 # MIT

View File

@@ -6,7 +6,7 @@ description_file =
author = OpenStack
author_email = openstack-discuss@lists.openstack.org
home_page = https://docs.openstack.org/python-watcherclient/latest/
python_requires = >=3.10
python_requires = >=3.6
classifier =
Environment :: OpenStack
Intended Audience :: Information Technology
@@ -15,9 +15,9 @@ classifier =
Operating System :: POSIX :: Linux
Programming Language :: Python
Programming Language :: Python :: 3
Programming Language :: Python :: 3.10
Programming Language :: Python :: 3.11
Programming Language :: Python :: 3.12
Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
[files]
packages =
@@ -60,7 +60,6 @@ openstack.infra_optim.v1 =
optimize_action_show = watcherclient.v1.action_shell:ShowAction
optimize_action_list = watcherclient.v1.action_shell:ListAction
optimize_action_update = watcherclient.v1.action_shell:UpdateAction
optimize_scoringengine_show = watcherclient.v1.scoring_engine_shell:ShowScoringEngine
optimize_scoringengine_list = watcherclient.v1.scoring_engine_shell:ListScoringEngine
@@ -100,7 +99,6 @@ watcherclient.v1 =
action_show = watcherclient.v1.action_shell:ShowAction
action_list = watcherclient.v1.action_shell:ListAction
action_update = watcherclient.v1.action_shell:UpdateAction
scoringengine_show = watcherclient.v1.scoring_engine_shell:ShowScoringEngine
scoringengine_list = watcherclient.v1.scoring_engine_shell:ListScoringEngine

View File

@@ -1,7 +1,12 @@
# The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
coverage!=4.4,>=4.0 # Apache-2.0
fixtures>=3.0.0 # Apache-2.0/BSD
hacking>=7.0.0,<7.1.0 # Apache-2.0
hacking>=3.0.1,<3.1.0 # Apache-2.0
oslotest>=3.2.0 # Apache-2.0
python-subunit>=1.0.0 # Apache-2.0/BSD
stestr>=2.0.0 # Apache-2.0
testscenarios>=0.4 # Apache-2.0/BSD
testtools>=2.2.0 # MIT

13
tox.ini
View File

@@ -1,14 +1,17 @@
[tox]
minversion = 3.18.0
envlist = py3,pep8
skipsdist = True
[testenv]
usedevelop = True
passenv = ZUUL_CACHE_DIR
REQUIREMENTS_PIP_LOCATION
install_command = pip install {opts} {packages}
setenv =
VIRTUAL_ENV={envdir}
deps =
-c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/2025.2}
-c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/xena}
-r{toxinidir}/test-requirements.txt
-r{toxinidir}/requirements.txt
allowlist_externals =
@@ -40,13 +43,14 @@ commands =
[testenv:docs]
basepython = python3
deps =
-c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/2025.2}
-c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/xena}
-r{toxinidir}/doc/requirements.txt
commands = sphinx-build -W -b html doc/source doc/build/html
[testenv:pdf-docs]
basepython = python3
envdir = {toxworkdir}/docs
deps = {[testenv:docs]deps}
allowlist_externals =
rm
@@ -74,8 +78,3 @@ commands = python setup.py bdist_wheel
[hacking]
import_exceptions = watcherclient._i18n
[testenv:functional]
passenv = OS_*
commands =
stestr --test-path=./watcherclient/tests/client_functional/ run --concurrency=1 {posargs}

View File

@@ -32,4 +32,4 @@ API_MIN_VERSION = api_versioning.APIVersion("1.0")
# when client supported the max version, and bumped sequentially, otherwise
# the client may break due to server side new version may include some
# backward incompatible change.
API_MAX_VERSION = api_versioning.APIVersion("1.5")
API_MAX_VERSION = api_versioning.APIVersion("1.1")

View File

@@ -28,7 +28,6 @@ if not LOG.handlers:
MINOR_1_START_END_TIMING = '1.1'
MINOR_2_FORCE_AUDIT = '1.2'
MINOR_5_ACTION_UPDATE = '1.5'
HEADER_NAME = "OpenStack-API-Version"
# key is a deprecated version and value is an alternative version.
DEPRECATED_VERSIONS = {}
@@ -55,15 +54,6 @@ def launch_audit_forced(requested_version):
APIVersion(MINOR_2_FORCE_AUDIT))
def action_update_supported(requested_version):
"""Check if we should support action update functionality.
Version 1.5 of the API added support for updating action state.
"""
return (APIVersion(requested_version) >=
APIVersion(MINOR_5_ACTION_UPDATE))
class APIVersion(object):
"""This class represents an API Version Request.

View File

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

View File

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

View File

@@ -19,7 +19,8 @@ Base utilities to build API operation managers and objects on top of.
"""
import copy
from urllib import parse as urlparse
import six.moves.urllib.parse as urlparse
from watcherclient.common.apiclient import base

View File

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

View File

@@ -14,24 +14,24 @@
# under the License.
import copy
from distutils import version
import functools
import hashlib
import http.client
import io
import logging
import os
import re
import socket
import ssl
import textwrap
import time
from urllib import parse as urlparse
from keystoneauth1 import adapter
from keystoneauth1 import exceptions as kexceptions
from oslo_serialization import jsonutils
from oslo_utils import strutils
import requests
import six
from six.moves import http_client
import six.moves.urllib.parse as urlparse
from watcherclient._i18n import _
from watcherclient.common import api_versioning
@@ -41,7 +41,7 @@ from watcherclient import exceptions
# Record the latest version that this client was tested with.
DEFAULT_VER = '1.latest'
# Minor version 4 for adding webhook API
LAST_KNOWN_API_VERSION = 5
LAST_KNOWN_API_VERSION = 4
LATEST_VERSION = '1.{}'.format(LAST_KNOWN_API_VERSION)
LOG = logging.getLogger(__name__)
@@ -62,7 +62,7 @@ SUPPORTED_ENDPOINT_SCHEME = ('http', 'https')
def _trim_endpoint_api_version(url):
"""Trim API version and trailing slash from endpoint."""
return re.sub(f'{API_VERSION}$', '', url.rstrip('/'))
return url.rstrip('/').rstrip(API_VERSION)
def _extract_error_json(body):
@@ -123,19 +123,16 @@ class VersionNegotiationMixin(object):
% {'req': self.os_infra_optim_api_version,
'min': min_ver, 'max': max_ver}))
negotiated_ver = api_versioning.APIVersion(
self.os_infra_optim_api_version)
min_ver = api_versioning.APIVersion(min_ver)
max_ver = api_versioning.APIVersion(max_ver)
if negotiated_ver > max_ver:
negotiated_ver = max_ver
negotiated_ver = str(
min(version.StrictVersion(self.os_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_infra_optim_api_version = negotiated_ver.get_string()
LOG.debug('Negotiated API version is %s', negotiated_ver.get_string())
self.os_infra_optim_api_version = negotiated_ver
LOG.debug('Negotiated API version is %s', negotiated_ver)
return negotiated_ver
@@ -250,7 +247,7 @@ class HTTPClient(VersionNegotiationMixin):
if not self.session.verify:
curl.append('-k')
elif isinstance(self.session.verify, str):
elif isinstance(self.session.verify, six.string_types):
curl.append('--cacert %s' % self.session.verify)
if self.session.cert:
@@ -328,7 +325,7 @@ class HTTPClient(VersionNegotiationMixin):
# to servers that did not support microversions. Details here:
# http://specs.openstack.org/openstack/watcher-specs/specs/kilo/api-microversions.html#use-case-3b-new-client-communicating-with-a-old-watcher-user-specified # noqa
if resp.status_code == http.client.NOT_ACCEPTABLE:
if resp.status_code == http_client.NOT_ACCEPTABLE:
negotiated_ver = self.negotiate_version(self.session, resp)
kwargs['headers']['OpenStack-API-Version'] = (
' '.join(['infra-optim', negotiated_ver]))
@@ -360,21 +357,21 @@ class HTTPClient(VersionNegotiationMixin):
]
body_str = ''.join(body_list)
self.log_http_response(resp, body_str)
body_iter = io.StringIO(body_str)
body_iter = six.StringIO(body_str)
else:
self.log_http_response(resp)
if resp.status_code >= http.client.BAD_REQUEST:
if resp.status_code >= http_client.BAD_REQUEST:
error_json = _extract_error_json(body_str)
raise exceptions.from_response(
resp, error_json.get('faultstring'),
error_json.get('debuginfo'), method, url)
elif resp.status_code in (http.client.MOVED_PERMANENTLY,
http.client.FOUND,
http.client.USE_PROXY):
elif resp.status_code in (http_client.MOVED_PERMANENTLY,
http_client.FOUND,
http_client.USE_PROXY):
# Redirected. Reissue the request to the new location.
return self._http_request(resp['location'], method, **kwargs)
elif resp.status_code == http.client.MULTIPLE_CHOICES:
elif resp.status_code == http_client.MULTIPLE_CHOICES:
raise exceptions.from_response(resp, method=method, url=url)
return resp, body_iter
@@ -390,8 +387,8 @@ class HTTPClient(VersionNegotiationMixin):
resp, body_iter = self._http_request(url, method, **kwargs)
content_type = resp.headers.get('Content-Type')
if (resp.status_code in (http.client.NO_CONTENT,
http.client.RESET_CONTENT) or
if (resp.status_code in (http_client.NO_CONTENT,
http_client.RESET_CONTENT) or
content_type is None):
return resp, list()
@@ -413,7 +410,7 @@ class HTTPClient(VersionNegotiationMixin):
return self._http_request(url, method, **kwargs)
class VerifiedHTTPSConnection(http.client.HTTPSConnection):
class VerifiedHTTPSConnection(six.moves.http_client.HTTPSConnection):
"""httplib-compatible connection using client-side SSL authentication
:see http://code.activestate.com/recipes/
@@ -422,8 +419,9 @@ class VerifiedHTTPSConnection(http.client.HTTPSConnection):
def __init__(self, host, port, key_file=None, cert_file=None,
ca_file=None, timeout=None, insecure=False):
super(VerifiedHTTPSConnection, self).__init__(
self, host, port, key_file=key_file, cert_file=cert_file)
six.moves.http_client.HTTPSConnection.__init__(self, host, port,
key_file=key_file,
cert_file=cert_file)
self.key_file = key_file
self.cert_file = cert_file
if ca_file is not None:
@@ -437,6 +435,11 @@ class VerifiedHTTPSConnection(http.client.HTTPSConnection):
"""Connect to a host on a given (SSL) port.
If ca_file is pointing somewhere, use it to check Server Certificate.
Redefined/copied and extended from httplib.py:1105 (Python 2.6.x).
This is needed to pass cert_reqs=ssl.CERT_REQUIRED as parameter to
ssl.wrap_socket(), which forces SSL to check server certificate against
our client certificate.
"""
sock = socket.create_connection((self.host, self.port), self.timeout)
@@ -444,21 +447,17 @@ class VerifiedHTTPSConnection(http.client.HTTPSConnection):
self.sock = sock
self._tunnel()
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
if self.insecure is True:
context.check_hostname = False
context.verify_mode = ssl.CERT_NONE
kwargs = {'cert_reqs': ssl.CERT_NONE}
else:
context.load_verify_locations(self.ca_file)
kwargs = {'cert_reqs': ssl.CERT_REQUIRED, 'ca_certs': self.ca_file}
if self.cert_file:
kwargs['certfile'] = self.cert_file
if self.key_file:
context.load_cert_chain(self.cert_file, self.key_file)
else:
context.load_cert_chain(self.cert_file)
kwargs['keyfile'] = self.key_file
self.sock = context.wrap_socket(sock)
self.sock = ssl.wrap_socket(sock, **kwargs)
@staticmethod
def get_system_ca_file():
@@ -504,7 +503,7 @@ class SessionClient(VersionNegotiationMixin, adapter.LegacyJsonAdapter):
def _http_request(self, url, method, **kwargs):
kwargs.setdefault('user_agent', USER_AGENT)
kwargs.setdefault('auth', self.auth)
if isinstance(self.endpoint_override, str):
if isinstance(self.endpoint_override, six.string_types):
kwargs.setdefault(
'endpoint_override',
_trim_endpoint_api_version(self.endpoint_override)
@@ -528,22 +527,22 @@ class SessionClient(VersionNegotiationMixin, adapter.LegacyJsonAdapter):
resp = self.session.request(url, method,
raise_exc=False, **kwargs)
if resp.status_code == http.client.NOT_ACCEPTABLE:
if resp.status_code == http_client.NOT_ACCEPTABLE:
negotiated_ver = self.negotiate_version(self.session, resp)
kwargs['headers']['OpenStack-API-Version'] = (
' '.join(['infra-optim', negotiated_ver]))
return self._http_request(url, method, **kwargs)
if resp.status_code >= http.client.BAD_REQUEST:
if resp.status_code >= http_client.BAD_REQUEST:
error_json = _extract_error_json(resp.content)
raise exceptions.from_response(
resp, error_json.get('faultstring'),
error_json.get('debuginfo'), method, url)
elif resp.status_code in (http.client.MOVED_PERMANENTLY,
http.client.FOUND, http.client.USE_PROXY):
elif resp.status_code in (http_client.MOVED_PERMANENTLY,
http_client.FOUND, http_client.USE_PROXY):
# Redirected. Reissue the request to the new location.
location = resp.headers.get('location')
resp = self._http_request(location, method, **kwargs)
elif resp.status_code == http.client.MULTIPLE_CHOICES:
elif resp.status_code == http_client.MULTIPLE_CHOICES:
raise exceptions.from_response(resp, method=method, url=url)
return resp
@@ -559,7 +558,7 @@ class SessionClient(VersionNegotiationMixin, adapter.LegacyJsonAdapter):
body = resp.content
content_type = resp.headers.get('content-type', None)
status = resp.status_code
if (status in (http.client.NO_CONTENT, http.client.RESET_CONTENT) or
if (status in (http_client.NO_CONTENT, http_client.RESET_CONTENT) or
content_type is None):
return resp, list()
if 'application/json' in content_type:

View File

@@ -203,9 +203,7 @@ class WatcherShell(app.App):
LOG.info("END return value: %s", ret_val)
def main(argv=None):
if argv is None:
argv = sys.argv[1:]
def main(argv=sys.argv[1:]):
watcher_app = WatcherShell()
return watcher_app.run(argv)

View File

@@ -1,141 +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
from tempest.lib.cli import output_parser
from tempest.lib import exceptions
def credentials():
# You can get credentials from OS environment.
creds_dict = {
'--os-username': os.environ.get('OS_USERNAME'),
'--os-password': os.environ.get('OS_PASSWORD'),
'--os-project-name': os.environ.get('OS_PROJECT_NAME'),
'--os-auth-url': os.environ.get('OS_AUTH_URL'),
'--os-project-domain-name': os.environ.get('OS_PROJECT_DOMAIN_ID'),
'--os-user-domain-name': os.environ.get('OS_USER_DOMAIN_ID'),
}
return [x for sub in creds_dict.items() for x in sub]
def execute(cmd, fail_ok=False, merge_stderr=True):
"""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(r'^\+\-[\+\-]+\-\+$')
api_version = 1.0
@classmethod
def watcher(cls, cmd, fail_ok=False):
"""Executes watcherclient command for the given action."""
return execute(
'openstack optimize --os-infra-optim-api-version {0} {1}'.format(
cls.api_version, cmd), fail_ok=fail_ok)
@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 item.keys():
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):
audit = cls.parse_show_as_object(
cls.watcher('audit show %s' % audit_uuid))
if audit['Audit Type'] == 'ONESHOT':
return audit['State'] == 'SUCCEEDED'
else:
return audit['State'] == 'ONGOING'

View File

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

View File

@@ -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.client_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
))

View File

@@ -1,212 +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 datetime import datetime
from dateutil import tz
import functools
from oslo_utils import uuidutils
from tempest.lib.common.utils import test_utils
from watcherclient.tests.client_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 AuditTestsV11(AuditTests):
"""This class tests v1.1 of Watcher API"""
api_version = 1.1
detailed_list_fields = AuditTests.list_fields + [
'Created At', 'Updated At', 'Deleted At', 'Parameters', 'Interval',
'Audit Scope', 'Next Run Time', 'Hostname', 'Start Time', 'End Time']
def test_audit_detailed_list(self):
raw_output = self.watcher('audit list --detail')
self.assert_table_structure([raw_output], self.detailed_list_fields)
def test_audit_show(self):
audit = self.watcher('audit show ' + self.audit_uuid)
self.assertIn(self.audit_uuid, audit)
self.assert_table_structure([audit], self.detailed_list_fields)
def test_audit_update(self):
local_time = datetime.now(tz.tzlocal())
local_time_str = local_time.strftime("%Y-%m-%dT%H:%M:%S")
utc_time = (local_time - local_time.utcoffset())
utc_time_str = utc_time.strftime("%Y-%m-%dT%H:%M:%S")
audit_raw_output = self.watcher(
'audit update {0} replace end_time="{1}"'.format(self.audit_uuid,
local_time_str))
audit_output = self.parse_show_as_object(audit_raw_output)
assert audit_output['End Time'] == utc_time_str
class AuditTestsV12(AuditTestsV11):
"""This class tests v1.2 of Watcher API"""
api_version = 1.2
@classmethod
def setUpClass(cls):
raw_output = cls.watcher('audittemplate create %s dummy -s dummy'
% cls.audit_template_name)
template_output = cls.parse_show_as_object(raw_output)
audit_raw_output = cls.watcher(
'audit create --force -a %s' % template_output['Name'])
audit_output = cls.parse_show_as_object(audit_raw_output)
cls.audit_uuid = audit_output['UUID']
audit_created = test_utils.call_until_true(
func=functools.partial(cls.has_audit_created, cls.audit_uuid),
duration=600,
sleep_for=2)
if not audit_created:
raise Exception('Audit has not been succeeded')
class AuditActiveTests(base.TestCase):
list_fields = ['UUID', 'Name', 'Audit Type', 'State', 'Goal', 'Strategy']
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_oneshot_audit(self):
audit = self.watcher('audit create -a %s' % self.audit_template_name)
audit_uuid = self.parse_show_as_object(audit)['UUID']
self.assert_table_structure([audit], self.detailed_list_fields)
self._delete_audit(audit_uuid)
def test_delete_oneshot_audit(self):
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)
def test_continuous_audit(self):
audit = self.watcher('audit create -a %s -t CONTINUOUS -i 600'
% self.audit_template_name)
audit_uuid = self.parse_show_as_object(audit)['UUID']
self.assert_table_structure([audit], self.detailed_list_fields)
self.assertTrue(test_utils.call_until_true(
func=functools.partial(
self.has_audit_created, audit_uuid),
duration=600,
sleep_for=2
))
audit_state = self.parse_show_as_object(
self.watcher('audit show %s' % audit_uuid))['State']
if audit_state == 'ONGOING':
self.watcher('audit update %s replace state=CANCELLED'
% audit_uuid)
raw_output = self.watcher('audit delete %s' % audit_uuid)
self.assertOutput('', raw_output)
outputs = self.parse_listing(
self.watcher('actionplan list --audit %s' % audit_uuid))
for actionplan in outputs:
self.watcher('actionplan delete %s' % actionplan['UUID'])

View File

@@ -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.client_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)

View File

@@ -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.client_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)

View File

@@ -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.client_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)

View File

@@ -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.client_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)

View File

@@ -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.client_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)

View File

@@ -148,22 +148,3 @@ class GetAPIVersionTestCase(utils.BaseTestCase):
self.assertEqual(mock_apiversion.return_value,
api_versioning.get_api_version(version))
mock_apiversion.assert_called_once_with(version)
class APIVersionFunctionsTestCase(utils.BaseTestCase):
def test_action_update_supported_true(self):
# Test versions >= 1.5 support action update
self.assertTrue(api_versioning.action_update_supported("1.5"))
self.assertTrue(api_versioning.action_update_supported("1.6"))
self.assertTrue(api_versioning.action_update_supported("2.0"))
def test_action_update_supported_false(self):
# Test versions < 1.5 do not support action update
self.assertFalse(api_versioning.action_update_supported("1.0"))
self.assertFalse(api_versioning.action_update_supported("1.1"))
self.assertFalse(api_versioning.action_update_supported("1.4"))
def test_action_update_supported_edge_case(self):
# Test exact boundary
self.assertTrue(api_versioning.action_update_supported("1.5"))
self.assertFalse(api_versioning.action_update_supported("1.4"))

View File

@@ -358,8 +358,3 @@ class ClientTest(utils.BaseTestCase):
client = httpclient.HTTPClient(endpoint)
conn_url = client._make_connection_url(url)
self.assertEqual(expected_url, conn_url)
def test_port_ends_with_one(self):
endpoint = "http://localhost:8081/"
http_client = httpclient.HTTPClient(endpoint)
self.assertEqual(endpoint, http_client._make_connection_url(""))

View File

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

View File

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

View File

@@ -14,10 +14,10 @@
# limitations under the License.
import datetime
import io
from unittest import mock
from oslo_utils.uuidutils import generate_uuid
import six
from watcherclient import exceptions
from watcherclient import shell
@@ -79,9 +79,8 @@ class ActionPlanShellTest(base.CommandTestCase):
FIELD_LABELS = resource_fields.ACTION_PLAN_FIELD_LABELS
GLOBAL_EFFICACY_FIELDS = resource_fields.GLOBAL_EFFICACY_FIELDS
def setUp(self, os_infra_optim_api_version='1.0'):
super(ActionPlanShellTest, self).setUp(
os_infra_optim_api_version=os_infra_optim_api_version)
def setUp(self):
super(self.__class__, self).setUp()
p_audit_manager = mock.patch.object(resource, 'AuditManager')
p_audit_template_manager = mock.patch.object(
@@ -105,7 +104,7 @@ class ActionPlanShellTest(base.CommandTestCase):
self.m_audit_template_mgr_cls.return_value = self.m_audit_template_mgr
self.m_action_plan_mgr_cls.return_value = self.m_action_plan_mgr
self.stdout = io.StringIO()
self.stdout = six.StringIO()
self.cmd = shell.WatcherShell(stdout=self.stdout)
def test_do_action_plan_list(self):
@@ -337,14 +336,3 @@ class ActionPlanShellTest(base.CommandTestCase):
self.assertEqual(1, exit_code)
self.assertEqual('', result)
class ActionPlanShellTest15(ActionPlanShellTest):
def setUp(self):
super(ActionPlanShellTest15, self).setUp(
os_infra_optim_api_version='1.5')
v15 = dict(status_message=None)
for action_plan in (ACTION_PLAN_1, ACTION_PLAN_2):
action_plan.update(v15)
self.FIELDS.extend(['status_message'])
self.FIELD_LABELS.extend(['Status Message'])

View File

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

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

@@ -14,9 +14,10 @@
# under the License.
import datetime
import io
from unittest import mock
import six
from watcherclient import shell
from watcherclient.tests.unit.v1 import base
from watcherclient import v1 as resource
@@ -163,7 +164,7 @@ class AuditShellTest(base.CommandTestCase):
self.m_audit_template_mgr_cls.return_value = self.m_audit_template_mgr
# stdout mock
self.stdout = io.StringIO()
self.stdout = six.StringIO()
self.cmd = shell.WatcherShell(stdout=self.stdout)
def test_do_audit_list(self):
@@ -494,9 +495,8 @@ class AuditShellTestv11(AuditShellTest):
class AuditShellTestv12(AuditShellTest):
def setUp(self, os_infra_optim_api_version='1.2'):
super(AuditShellTestv12, self).setUp(
os_infra_optim_api_version=os_infra_optim_api_version)
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):
@@ -698,13 +698,3 @@ class AuditShellTestv12(AuditShellTest):
name='my_audit',
force=False
)
class AuditShellTestv15(AuditShellTestv12):
def setUp(self):
super(AuditShellTestv15, self).setUp(os_infra_optim_api_version='1.5')
v15 = dict(status_message=None)
for audit in (self.AUDIT_1, self.AUDIT_2, self.AUDIT_3):
audit.update(v15)
self.FIELDS.extend(['status_message'])
self.FIELD_LABELS.extend(['Status Message'])

View File

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

View File

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

View File

@@ -13,9 +13,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import io
from unittest import mock
import six
from watcherclient import shell
from watcherclient.tests.unit.v1 import base
from watcherclient import v1 as resource
@@ -91,7 +92,7 @@ class DataModelShellTest(base.CommandTestCase):
self.m_data_model_mgr_cls.return_value = self.m_data_model_mgr
self.stdout = io.StringIO()
self.stdout = six.StringIO()
self.cmd = shell.WatcherShell(stdout=self.stdout)
def test_do_data_model_list(self):

View File

@@ -14,9 +14,10 @@
# limitations under the License.
import datetime
import io
from unittest import mock
import six
from watcherclient import shell
from watcherclient.tests.unit.v1 import base
from watcherclient import v1 as resource
@@ -72,7 +73,7 @@ class GoalShellTest(base.CommandTestCase):
self.m_goal_mgr = mock.Mock()
self.m_goal_mgr_cls.return_value = self.m_goal_mgr
self.stdout = io.StringIO()
self.stdout = six.StringIO()
self.cmd = shell.WatcherShell(stdout=self.stdout)
def test_do_goal_list(self):

View File

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

View File

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

View File

@@ -14,10 +14,10 @@
# limitations under the License.
import datetime
import io
from unittest import mock
from oslo_serialization import jsonutils
import six
from watcherclient import shell
from watcherclient.tests.unit.v1 import base
@@ -69,7 +69,7 @@ class StrategyShellTest(base.CommandTestCase):
self.m_strategy_mgr = mock.Mock()
self.m_strategy_mgr_cls.return_value = self.m_strategy_mgr
self.stdout = io.StringIO()
self.stdout = six.StringIO()
self.cmd = shell.WatcherShell(stdout=self.stdout)
def test_do_strategy_list(self):

View File

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

View File

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

View File

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

View File

@@ -38,9 +38,6 @@ def drop_unsupported_field(app_args, fields, field_labels):
if not api_versioning.launch_audit_forced(api_ver):
fields.remove('force')
field_labels.remove('Force')
if not api_versioning.action_update_supported(api_ver):
fields.remove('status_message')
field_labels.remove('Status Message')
return fields, field_labels

View File

@@ -14,9 +14,8 @@
# License for the specific language governing permissions and limitations
# under the License.
import io
from osc_lib import utils
import six
from watcherclient._i18n import _
from watcherclient.common import command
@@ -38,7 +37,7 @@ class ShowGoal(command.ShowOne):
return parser
def _format_indicator_spec_table(self, spec, parsed_args):
out = io.StringIO()
out = six.StringIO()
self.formatter.emit_one(
column_names=list(field.capitalize() for field in spec.keys()),
data=utils.get_dict_properties(spec, spec.keys()),
@@ -108,7 +107,7 @@ class ListGoal(command.Lister):
return parser
def _format_indicator_spec_table(self, goal, parsed_args):
out = io.StringIO()
out = six.StringIO()
efficacy_specification = goal.efficacy_specification
fields = ['name', 'unit']
self.formatter.emit_list(

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

@@ -33,14 +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', 'start_time', 'end_time', 'force',
'status_message']
'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', 'Start Time', 'End Time',
'Force', 'Status Message']
'Force']
AUDIT_SHORT_LIST_FIELDS = ['uuid', 'name', 'audit_type',
'state', 'goal_name', 'strategy_name',
@@ -52,13 +51,12 @@ AUDIT_SHORT_LIST_FIELD_LABELS = ['UUID', 'Name', 'Audit Type', 'State', 'Goal',
# Action Plan
ACTION_PLAN_FIELDS = ['uuid', 'created_at', 'updated_at', 'deleted_at',
'audit_uuid', 'strategy_name', 'state',
'efficacy_indicators', 'global_efficacy', 'hostname',
'status_message']
'efficacy_indicators', 'global_efficacy', 'hostname']
ACTION_PLAN_FIELD_LABELS = ['UUID', 'Created At', 'Updated At', 'Deleted At',
'Audit', 'Strategy', 'State',
'Efficacy indicators', 'Global efficacy',
'Hostname', 'Status Message']
'Hostname']
ACTION_PLAN_SHORT_LIST_FIELDS = ['uuid', 'audit_uuid', 'state',
'updated_at', 'global_efficacy']
@@ -71,11 +69,11 @@ GLOBAL_EFFICACY_FIELDS = ['value', 'unit', 'name', 'description']
# Action
ACTION_FIELDS = ['uuid', 'created_at', 'updated_at', 'deleted_at', 'parents',
'state', 'action_plan_uuid', 'action_type',
'input_parameters', 'description', 'status_message']
'input_parameters', 'description']
ACTION_FIELD_LABELS = ['UUID', 'Created At', 'Updated At', 'Deleted At',
'Parents', 'State', 'Action Plan', 'Action',
'Parameters', 'Description', 'Status Message']
'Parameters', 'Description']
ACTION_SHORT_LIST_FIELDS = ['uuid', 'parents',
'state', 'action_plan_uuid', 'action_type']

View File

@@ -13,7 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
from urllib import parse
import six.moves.urllib.parse as parse
from watcherclient.common import base
from watcherclient.common import utils

View File

@@ -71,7 +71,7 @@ class StateStrategy(command.Lister):
def _format_spec(self, requirements):
for req in requirements:
if isinstance(req.state, list):
if type(req.state) == list:
req.state = jsonutils.dumps(req.state, indent=2)
return requirements