Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3597c6a7bd | |||
| e7b9945933 | |||
| a08c0fde6f |
@@ -2,3 +2,4 @@
|
||||
host=review.opendev.org
|
||||
port=29418
|
||||
project=openstack/python-watcherclient.git
|
||||
defaultbranch=unmaintained/zed
|
||||
|
||||
31
.zuul.yaml
31
.zuul.yaml
@@ -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-zed-jobs
|
||||
- publish-openstack-docs-pti
|
||||
- check-requirements
|
||||
- openstackclient-plugin-jobs
|
||||
check:
|
||||
jobs:
|
||||
- python-watcherclient-functional
|
||||
- watcherclient-tempest-functional
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
82
lower-constraints.txt
Normal 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
|
||||
@@ -1,3 +0,0 @@
|
||||
[build-system]
|
||||
requires = ["pbr>=6.0.0", "setuptools>=64.0.0"]
|
||||
build-backend = "pbr.build"
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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
|
||||
|
||||
17
setup.cfg
17
setup.cfg
@@ -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.8
|
||||
classifier =
|
||||
Environment :: OpenStack
|
||||
Intended Audience :: Information Technology
|
||||
@@ -15,11 +15,8 @@ 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.13
|
||||
Programming Language :: Python :: 3 :: Only
|
||||
Programming Language :: Python :: 3.8
|
||||
Programming Language :: Python :: 3.9
|
||||
|
||||
[files]
|
||||
packages =
|
||||
@@ -62,7 +59,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
|
||||
@@ -102,7 +98,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
|
||||
@@ -111,3 +106,9 @@ watcherclient.v1 =
|
||||
service_list = watcherclient.v1.service_shell:ListService
|
||||
|
||||
datamodel_list = watcherclient.v1.data_model_shell:ListDataModel
|
||||
|
||||
[pbr]
|
||||
autodoc_index_modules = True
|
||||
autodoc_exclude_modules =
|
||||
watcherclient.tests.*
|
||||
api_doc_dir = reference/api
|
||||
|
||||
@@ -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
13
tox.ini
@@ -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/master}
|
||||
-c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/zed}
|
||||
-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/master}
|
||||
-c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/zed}
|
||||
-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}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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'
|
||||
@@ -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)
|
||||
@@ -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
|
||||
))
|
||||
@@ -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'])
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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"))
|
||||
|
||||
@@ -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(""))
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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'])
|
||||
|
||||
@@ -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
20
watcherclient/tests/unit/v1/test_audit_shell.py
Normal file → Executable 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'])
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
14
watcherclient/v1/resource_fields.py
Normal file → Executable 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']
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user