Remove watcher_tempest_plugin
In accordance with Queens global goal[1], this patch set removes watcher tempest plugin from watcher repository since it is stored as independent repository[2]. Jenkins job gate-watcher-dsvm-multinode-ubuntu-xenial-nv has been modified, it uses watcher-tempest-plugin repo now. [1]: https://governance.openstack.org/tc/goals/queens/split-tempest-plugins.html [2]: http://git.openstack.org/cgit/openstack/watcher-tempest-plugin/ Change-Id: I4d1207fbd73ee2519a6d40342f5fd3c5d3ee8bc7
This commit is contained in:
parent
1b413f5536
commit
0c4b439c5e
@ -47,4 +47,12 @@ When you're done, deactivate the virtualenv::
|
||||
|
||||
$ deactivate
|
||||
|
||||
.. include:: ../../../watcher_tempest_plugin/README.rst
|
||||
.. _tempest_tests:
|
||||
|
||||
Tempest tests
|
||||
=============
|
||||
|
||||
Tempest tests for Watcher has been migrated to the external repo
|
||||
`watcher-tempest-plugin`_.
|
||||
|
||||
.. _watcher-tempest-plugin: https://github.com/openstack/watcher-tempest-plugin
|
||||
|
@ -21,7 +21,6 @@ classifier =
|
||||
[files]
|
||||
packages =
|
||||
watcher
|
||||
watcher_tempest_plugin
|
||||
data_files =
|
||||
etc/ = etc/*
|
||||
|
||||
@ -40,9 +39,6 @@ console_scripts =
|
||||
watcher-applier = watcher.cmd.applier:main
|
||||
watcher-sync = watcher.cmd.sync:main
|
||||
|
||||
tempest.test_plugins =
|
||||
watcher_tests = watcher_tempest_plugin.plugin:WatcherTempestPlugin
|
||||
|
||||
watcher.database.migration_backend =
|
||||
sqlalchemy = watcher.db.sqlalchemy.migration
|
||||
|
||||
@ -103,7 +99,6 @@ autodoc_exclude_modules =
|
||||
watcher.db.sqlalchemy.alembic.env
|
||||
watcher.db.sqlalchemy.alembic.versions.*
|
||||
watcher.tests.*
|
||||
watcher_tempest_plugin.*
|
||||
watcher.doc
|
||||
|
||||
|
||||
|
@ -1,158 +0,0 @@
|
||||
..
|
||||
Except where otherwise noted, this document is licensed under Creative
|
||||
Commons Attribution 3.0 License. You can view the license at:
|
||||
|
||||
https://creativecommons.org/licenses/by/3.0/
|
||||
|
||||
.. _tempest_tests:
|
||||
|
||||
Tempest tests
|
||||
=============
|
||||
|
||||
The following procedure gets you started with Tempest testing but you can also
|
||||
refer to the `Tempest documentation`_ for more details.
|
||||
|
||||
.. _Tempest documentation: https://docs.openstack.org/tempest/latest
|
||||
|
||||
|
||||
Tempest installation
|
||||
--------------------
|
||||
|
||||
To install Tempest you can issue the following commands::
|
||||
|
||||
$ git clone https://github.com/openstack/tempest/
|
||||
$ cd tempest/
|
||||
$ pip install .
|
||||
|
||||
The folder you are into now will be called ``<TEMPEST_DIR>`` from now onwards.
|
||||
|
||||
Please note that although it is fully working outside a virtual environment, it
|
||||
is recommended to install within a `venv`.
|
||||
|
||||
|
||||
Watcher Tempest testing setup
|
||||
-----------------------------
|
||||
|
||||
You can now install Watcher alongside it in development mode by issuing the
|
||||
following command::
|
||||
|
||||
$ pip install -e <WATCHER_SRC_DIR>
|
||||
|
||||
Then setup a local working environment (here ``watcher-cloud``) for running
|
||||
Tempest for Watcher which shall contain the configuration for your OpenStack
|
||||
integration platform.
|
||||
|
||||
In a virtual environment, you can do so by issuing the following command::
|
||||
|
||||
$ cd <TEMPEST_DIR>
|
||||
$ tempest init watcher-cloud
|
||||
|
||||
Otherwise, if you are not using a virtualenv::
|
||||
|
||||
$ cd <TEMPEST_DIR>
|
||||
$ tempest init --config-dir ./etc watcher-cloud
|
||||
|
||||
By default the configuration file is empty so before starting, you need to
|
||||
issue the following commands::
|
||||
|
||||
$ cd <TEMPEST_DIR>/watcher-cloud/etc
|
||||
$ cp tempest.conf.sample tempest.conf
|
||||
|
||||
At this point you need to edit the ``watcher-cloud/etc/tempest.conf``
|
||||
file as described in the `Tempest configuration guide`_.
|
||||
Shown below is a minimal configuration you need to set within your
|
||||
``tempest.conf`` configuration file which can get you started.
|
||||
|
||||
For Keystone V3::
|
||||
|
||||
[identity]
|
||||
uri_v3 = http://<KEYSTONE_PUBLIC_ENDPOINT_IP>:<KEYSTONE_PORT>/v3
|
||||
auth_version = v3
|
||||
|
||||
[auth]
|
||||
admin_username = <ADMIN_USERNAME>
|
||||
admin_password = <ADMIN_PASSWORD>
|
||||
admin_tenant_name = <ADMIN_TENANT_NAME>
|
||||
admin_domain_name = <ADMIN_DOMAIN_NAME>
|
||||
|
||||
[identity-feature-enabled]
|
||||
api_v2 = false
|
||||
api_v3 = true
|
||||
|
||||
For Keystone V2::
|
||||
|
||||
[identity]
|
||||
uri = http://<KEYSTONE_PUBLIC_ENDPOINT_IP>:<KEYSTONE_PORT>/v2.0
|
||||
auth_version = v2
|
||||
|
||||
[auth]
|
||||
admin_tenant_name = <ADMIN_TENANT_NAME>
|
||||
admin_username = <ADMIN_USERNAME>
|
||||
admin_password = <ADMIN_PASSWORD>
|
||||
|
||||
In both cases::
|
||||
|
||||
[network]
|
||||
public_network_id = <PUBLIC_NETWORK_ID>
|
||||
|
||||
You now have the minimum configuration for running Watcher Tempest tests on a
|
||||
single node.
|
||||
|
||||
Since deploying Watcher with only a single compute node is not very useful, a
|
||||
few more configuration have to be set in your ``tempest.conf`` file in order to
|
||||
enable the execution of multi-node scenarios::
|
||||
|
||||
[compute]
|
||||
# To indicate Tempest test that you have provided enough compute nodes
|
||||
min_compute_nodes = 2
|
||||
|
||||
# Image UUID you can get using the "glance image-list" command
|
||||
image_ref = <IMAGE_UUID>
|
||||
|
||||
|
||||
For more information, please refer to:
|
||||
|
||||
- Keystone connection: https://docs.openstack.org/tempest/latest/configuration.html#keystone-connection-info
|
||||
- Dynamic Keystone Credentials: https://docs.openstack.org/tempest/latest/configuration.html#dynamic-credentials
|
||||
|
||||
.. _virtual environment: http://docs.python-guide.org/en/latest/dev/virtualenvs/
|
||||
.. _Tempest configuration guide: http://docs.openstack.org/tempest/latest/configuration.html
|
||||
|
||||
|
||||
Watcher Tempest tests execution
|
||||
-------------------------------
|
||||
|
||||
To list all Watcher Tempest cases, you can issue the following commands::
|
||||
|
||||
$ cd <TEMPEST_DIR>
|
||||
$ testr list-tests watcher
|
||||
|
||||
To run only these tests in Tempest, you can then issue these commands::
|
||||
|
||||
$ ./run_tempest.sh --config watcher-cloud/etc/tempest.conf -N -- watcher
|
||||
|
||||
Or alternatively the following commands if you are::
|
||||
|
||||
$ cd <TEMPEST_DIR>/watcher-cloud
|
||||
$ ../run_tempest.sh -N -- watcher
|
||||
|
||||
To run a single test case, go to Tempest directory, then run with test case
|
||||
name, e.g.::
|
||||
|
||||
$ cd <TEMPEST_DIR>
|
||||
$ ./run_tempest.sh --config watcher-cloud/etc/tempest.conf -N \
|
||||
-- watcher_tempest_plugin.tests.api.admin.test_audit_template.TestCreateDeleteAuditTemplate.test_create_audit_template
|
||||
|
||||
Alternatively, you can also run the Watcher Tempest plugin tests using tox. But
|
||||
before you can do so, you need to follow the Tempest explanation on running
|
||||
`tox with plugins`_. Then, run::
|
||||
|
||||
$ export TEMPEST_CONFIG_DIR=<TEMPEST_DIR>/watcher-cloud/etc/
|
||||
$ tox -eall-plugin watcher
|
||||
|
||||
.. _tox with plugins: https://docs.openstack.org/tempest/latest/plugin.html#notes-for-using-plugins-with-virtualenvs
|
||||
|
||||
And, to run a specific test::
|
||||
|
||||
$ export TEMPEST_CONFIG_DIR=<TEMPEST_DIR>/watcher-cloud/etc/
|
||||
$ tox -eall-plugin watcher_tempest_plugin.tests.api.admin.test_audit_template.TestCreateDeleteAuditTemplate.test_create_audit_template
|
@ -1,23 +0,0 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2016 b<>com
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
|
||||
service_option = cfg.BoolOpt("watcher",
|
||||
default=True,
|
||||
help="Whether or not watcher is expected to be "
|
||||
"available")
|
@ -1,42 +0,0 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2016 b<>com
|
||||
#
|
||||
# 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 abc
|
||||
|
||||
import six
|
||||
from tempest import clients
|
||||
from tempest.common import credentials_factory as creds_factory
|
||||
from tempest import config
|
||||
|
||||
from watcher_tempest_plugin.services.infra_optim.v1.json import client as ioc
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class BaseManager(clients.Manager):
|
||||
|
||||
def __init__(self, credentials):
|
||||
super(BaseManager, self).__init__(credentials)
|
||||
self.io_client = ioc.InfraOptimClientJSON(
|
||||
self.auth_provider, 'infra-optim', CONF.identity.region)
|
||||
|
||||
|
||||
class AdminManager(BaseManager):
|
||||
def __init__(self):
|
||||
super(AdminManager, self).__init__(
|
||||
creds_factory.get_configured_admin_credentials(),
|
||||
)
|
@ -1,34 +0,0 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
|
||||
import os
|
||||
|
||||
from tempest.test_discover import plugins
|
||||
|
||||
from watcher_tempest_plugin import config as watcher_config
|
||||
|
||||
|
||||
class WatcherTempestPlugin(plugins.TempestPlugin):
|
||||
def load_tests(self):
|
||||
base_path = os.path.split(os.path.dirname(
|
||||
os.path.abspath(__file__)))[0]
|
||||
test_dir = "watcher_tempest_plugin/tests"
|
||||
full_test_dir = os.path.join(base_path, test_dir)
|
||||
return full_test_dir, base_path
|
||||
|
||||
def register_opts(self, conf):
|
||||
conf.register_opt(watcher_config.service_option,
|
||||
group='service_available')
|
||||
|
||||
def get_opt_lists(self):
|
||||
return [('service_available', [watcher_config.service_option])]
|
@ -1,211 +0,0 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2016 b<>com
|
||||
#
|
||||
# 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 abc
|
||||
import functools
|
||||
|
||||
import six
|
||||
import six.moves.urllib.parse as urlparse
|
||||
|
||||
from tempest.lib.common import rest_client
|
||||
|
||||
|
||||
def handle_errors(f):
|
||||
"""A decorator that allows to ignore certain types of errors."""
|
||||
|
||||
@functools.wraps(f)
|
||||
def wrapper(*args, **kwargs):
|
||||
param_name = 'ignore_errors'
|
||||
ignored_errors = kwargs.get(param_name, tuple())
|
||||
|
||||
if param_name in kwargs:
|
||||
del kwargs[param_name]
|
||||
|
||||
try:
|
||||
return f(*args, **kwargs)
|
||||
except ignored_errors:
|
||||
# Silently ignore errors
|
||||
pass
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class BaseInfraOptimClient(rest_client.RestClient):
|
||||
"""Base Tempest REST client for Watcher API."""
|
||||
|
||||
URI_PREFIX = ''
|
||||
|
||||
@abc.abstractmethod
|
||||
def serialize(self, object_dict):
|
||||
"""Serialize an Watcher object."""
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractmethod
|
||||
def deserialize(self, object_str):
|
||||
"""Deserialize an Watcher object."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def _get_uri(self, resource_name, uuid=None, permanent=False):
|
||||
"""Get URI for a specific resource or object.
|
||||
|
||||
:param resource_name: The name of the REST resource, e.g., 'audits'.
|
||||
:param uuid: The unique identifier of an object in UUID format.
|
||||
:return: Relative URI for the resource or object.
|
||||
"""
|
||||
|
||||
prefix = self.URI_PREFIX if not permanent else ''
|
||||
|
||||
return '{pref}/{res}{uuid}'.format(pref=prefix,
|
||||
res=resource_name,
|
||||
uuid='/%s' % uuid if uuid else '')
|
||||
|
||||
def _make_patch(self, allowed_attributes, **kw):
|
||||
"""Create a JSON patch according to RFC 6902.
|
||||
|
||||
:param allowed_attributes: An iterable object that contains a set of
|
||||
allowed attributes for an object.
|
||||
:param **kw: Attributes and new values for them.
|
||||
:return: A JSON path that sets values of the specified attributes to
|
||||
the new ones.
|
||||
"""
|
||||
|
||||
def get_change(kw, path='/'):
|
||||
for name, value in kw.items():
|
||||
if isinstance(value, dict):
|
||||
for ch in get_change(value, path + '%s/' % name):
|
||||
yield ch
|
||||
else:
|
||||
if value is None:
|
||||
yield {'path': path + name,
|
||||
'op': 'remove'}
|
||||
else:
|
||||
yield {'path': path + name,
|
||||
'value': value,
|
||||
'op': 'replace'}
|
||||
|
||||
patch = [ch for ch in get_change(kw)
|
||||
if ch['path'].lstrip('/') in allowed_attributes]
|
||||
|
||||
return patch
|
||||
|
||||
def _list_request(self, resource, permanent=False, **kwargs):
|
||||
"""Get the list of objects of the specified type.
|
||||
|
||||
:param resource: The name of the REST resource, e.g., 'audits'.
|
||||
"param **kw: Parameters for the request.
|
||||
:return: A tuple with the server response and deserialized JSON list
|
||||
of objects
|
||||
"""
|
||||
|
||||
uri = self._get_uri(resource, permanent=permanent)
|
||||
if kwargs:
|
||||
uri += "?%s" % urlparse.urlencode(kwargs)
|
||||
|
||||
resp, body = self.get(uri)
|
||||
self.expected_success(200, int(resp['status']))
|
||||
|
||||
return resp, self.deserialize(body)
|
||||
|
||||
def _show_request(self, resource, uuid, permanent=False, **kwargs):
|
||||
"""Gets a specific object of the specified type.
|
||||
|
||||
:param uuid: Unique identifier of the object in UUID format.
|
||||
:return: Serialized object as a dictionary.
|
||||
"""
|
||||
|
||||
if 'uri' in kwargs:
|
||||
uri = kwargs['uri']
|
||||
else:
|
||||
uri = self._get_uri(resource, uuid=uuid, permanent=permanent)
|
||||
resp, body = self.get(uri)
|
||||
self.expected_success(200, int(resp['status']))
|
||||
|
||||
return resp, self.deserialize(body)
|
||||
|
||||
def _create_request(self, resource, object_dict):
|
||||
"""Create an object of the specified type.
|
||||
|
||||
:param resource: The name of the REST resource, e.g., 'audits'.
|
||||
:param object_dict: A Python dict that represents an object of the
|
||||
specified type.
|
||||
:return: A tuple with the server response and the deserialized created
|
||||
object.
|
||||
"""
|
||||
|
||||
body = self.serialize(object_dict)
|
||||
uri = self._get_uri(resource)
|
||||
|
||||
resp, body = self.post(uri, body=body)
|
||||
self.expected_success(201, int(resp['status']))
|
||||
|
||||
return resp, self.deserialize(body)
|
||||
|
||||
def _delete_request(self, resource, uuid):
|
||||
"""Delete specified object.
|
||||
|
||||
:param resource: The name of the REST resource, e.g., 'audits'.
|
||||
:param uuid: The unique identifier of an object in UUID format.
|
||||
:return: A tuple with the server response and the response body.
|
||||
"""
|
||||
|
||||
uri = self._get_uri(resource, uuid)
|
||||
|
||||
resp, body = self.delete(uri)
|
||||
self.expected_success(204, int(resp['status']))
|
||||
return resp, body
|
||||
|
||||
def _patch_request(self, resource, uuid, patch_object):
|
||||
"""Update specified object with JSON-patch.
|
||||
|
||||
:param resource: The name of the REST resource, e.g., 'audits'.
|
||||
:param uuid: The unique identifier of an object in UUID format.
|
||||
:return: A tuple with the server response and the serialized patched
|
||||
object.
|
||||
"""
|
||||
|
||||
uri = self._get_uri(resource, uuid)
|
||||
patch_body = self.serialize(patch_object)
|
||||
|
||||
resp, body = self.patch(uri, body=patch_body)
|
||||
self.expected_success(200, int(resp['status']))
|
||||
return resp, self.deserialize(body)
|
||||
|
||||
@handle_errors
|
||||
def get_api_description(self):
|
||||
"""Retrieves all versions of the Watcher API."""
|
||||
|
||||
return self._list_request('', permanent=True)
|
||||
|
||||
@handle_errors
|
||||
def get_version_description(self, version='v1'):
|
||||
"""Retrieves the description of the API.
|
||||
|
||||
:param version: The version of the API. Default: 'v1'.
|
||||
:return: Serialized description of API resources.
|
||||
"""
|
||||
|
||||
return self._list_request(version, permanent=True)
|
||||
|
||||
def _put_request(self, resource, put_object):
|
||||
"""Update specified object with JSON-patch."""
|
||||
|
||||
uri = self._get_uri(resource)
|
||||
put_body = self.serialize(put_object)
|
||||
|
||||
resp, body = self.put(uri, body=put_body)
|
||||
self.expected_success(202, int(resp['status']))
|
||||
return resp, body
|
@ -1,331 +0,0 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2016 b<>com
|
||||
#
|
||||
# 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_serialization import jsonutils
|
||||
from watcher.common import utils
|
||||
from watcher_tempest_plugin.services.infra_optim import base
|
||||
|
||||
|
||||
class InfraOptimClientJSON(base.BaseInfraOptimClient):
|
||||
"""Base Tempest REST client for Watcher API v1."""
|
||||
|
||||
URI_PREFIX = 'v1'
|
||||
|
||||
def serialize(self, object_dict):
|
||||
"""Serialize an Watcher object."""
|
||||
return jsonutils.dumps(object_dict)
|
||||
|
||||
def deserialize(self, object_str):
|
||||
"""Deserialize an Watcher object."""
|
||||
return jsonutils.loads(object_str.decode('utf-8'))
|
||||
|
||||
# ### AUDIT TEMPLATES ### #
|
||||
|
||||
@base.handle_errors
|
||||
def list_audit_templates(self, **kwargs):
|
||||
"""List all existing audit templates."""
|
||||
return self._list_request('audit_templates', **kwargs)
|
||||
|
||||
@base.handle_errors
|
||||
def list_audit_templates_detail(self, **kwargs):
|
||||
"""Lists details of all existing audit templates."""
|
||||
return self._list_request('/audit_templates/detail', **kwargs)
|
||||
|
||||
@base.handle_errors
|
||||
def show_audit_template(self, audit_template_uuid):
|
||||
"""Gets a specific audit template.
|
||||
|
||||
:param audit_template_uuid: Unique identifier of the audit template
|
||||
:return: Serialized audit template as a dictionary.
|
||||
"""
|
||||
return self._show_request('audit_templates', audit_template_uuid)
|
||||
|
||||
@base.handle_errors
|
||||
def create_audit_template(self, **kwargs):
|
||||
"""Creates an audit template with the specified parameters.
|
||||
|
||||
:param name: The name of the audit template.
|
||||
:param description: The description of the audit template.
|
||||
:param goal_uuid: The related Goal UUID associated.
|
||||
:param strategy_uuid: The related Strategy UUID associated.
|
||||
:param audit_scope: Scope the audit should apply to.
|
||||
:return: A tuple with the server response and the created audit
|
||||
template.
|
||||
"""
|
||||
|
||||
parameters = {k: v for k, v in kwargs.items() if v is not None}
|
||||
# This name is unique to avoid the DB unique constraint on names
|
||||
unique_name = 'Tempest Audit Template %s' % utils.generate_uuid()
|
||||
|
||||
audit_template = {
|
||||
'name': parameters.get('name', unique_name),
|
||||
'description': parameters.get('description'),
|
||||
'goal': parameters.get('goal'),
|
||||
'strategy': parameters.get('strategy'),
|
||||
'scope': parameters.get('scope', []),
|
||||
}
|
||||
|
||||
return self._create_request('audit_templates', audit_template)
|
||||
|
||||
@base.handle_errors
|
||||
def delete_audit_template(self, audit_template_uuid):
|
||||
"""Deletes an audit template having the specified UUID.
|
||||
|
||||
:param audit_template_uuid: The unique identifier of the audit template
|
||||
:return: A tuple with the server response and the response body.
|
||||
"""
|
||||
|
||||
return self._delete_request('audit_templates', audit_template_uuid)
|
||||
|
||||
@base.handle_errors
|
||||
def update_audit_template(self, audit_template_uuid, patch):
|
||||
"""Update the specified audit template.
|
||||
|
||||
:param audit_template_uuid: The unique identifier of the audit template
|
||||
:param patch: List of dicts representing json patches.
|
||||
:return: A tuple with the server response and the updated audit
|
||||
template.
|
||||
"""
|
||||
|
||||
return self._patch_request('audit_templates',
|
||||
audit_template_uuid, patch)
|
||||
|
||||
# ### AUDITS ### #
|
||||
|
||||
@base.handle_errors
|
||||
def list_audits(self, **kwargs):
|
||||
"""List all existing audit templates."""
|
||||
return self._list_request('audits', **kwargs)
|
||||
|
||||
@base.handle_errors
|
||||
def list_audits_detail(self, **kwargs):
|
||||
"""Lists details of all existing audit templates."""
|
||||
return self._list_request('/audits/detail', **kwargs)
|
||||
|
||||
@base.handle_errors
|
||||
def show_audit(self, audit_uuid):
|
||||
"""Gets a specific audit template.
|
||||
|
||||
:param audit_uuid: Unique identifier of the audit template
|
||||
:return: Serialized audit template as a dictionary
|
||||
"""
|
||||
return self._show_request('audits', audit_uuid)
|
||||
|
||||
@base.handle_errors
|
||||
def create_audit(self, audit_template_uuid, **kwargs):
|
||||
"""Create an audit with the specified parameters
|
||||
|
||||
:param audit_template_uuid: Audit template ID used by the audit
|
||||
:return: A tuple with the server response and the created audit
|
||||
"""
|
||||
audit = {'audit_template_uuid': audit_template_uuid}
|
||||
audit.update(kwargs)
|
||||
if not audit['state']:
|
||||
del audit['state']
|
||||
|
||||
return self._create_request('audits', audit)
|
||||
|
||||
@base.handle_errors
|
||||
def delete_audit(self, audit_uuid):
|
||||
"""Deletes an audit having the specified UUID
|
||||
|
||||
:param audit_uuid: The unique identifier of the audit
|
||||
:return: A tuple with the server response and the response body
|
||||
"""
|
||||
|
||||
return self._delete_request('audits', audit_uuid)
|
||||
|
||||
@base.handle_errors
|
||||
def update_audit(self, audit_uuid, patch):
|
||||
"""Update the specified audit.
|
||||
|
||||
:param audit_uuid: The unique identifier of the audit
|
||||
:param patch: List of dicts representing json patches.
|
||||
:return: Tuple with the server response and the updated audit
|
||||
"""
|
||||
|
||||
return self._patch_request('audits', audit_uuid, patch)
|
||||
|
||||
# ### ACTION PLANS ### #
|
||||
|
||||
@base.handle_errors
|
||||
def list_action_plans(self, **kwargs):
|
||||
"""List all existing action plan"""
|
||||
return self._list_request('action_plans', **kwargs)
|
||||
|
||||
@base.handle_errors
|
||||
def list_action_plans_detail(self, **kwargs):
|
||||
"""Lists details of all existing action plan"""
|
||||
return self._list_request('/action_plans/detail', **kwargs)
|
||||
|
||||
@base.handle_errors
|
||||
def show_action_plan(self, action_plan_uuid):
|
||||
"""Gets a specific action plan
|
||||
|
||||
:param action_plan_uuid: Unique identifier of the action plan
|
||||
:return: Serialized action plan as a dictionary
|
||||
"""
|
||||
return self._show_request('/action_plans', action_plan_uuid)
|
||||
|
||||
@base.handle_errors
|
||||
def delete_action_plan(self, action_plan_uuid):
|
||||
"""Deletes an action plan having the specified UUID
|
||||
|
||||
:param action_plan_uuid: The unique identifier of the action_plan
|
||||
:return: A tuple with the server response and the response body
|
||||
"""
|
||||
|
||||
return self._delete_request('/action_plans', action_plan_uuid)
|
||||
|
||||
@base.handle_errors
|
||||
def delete_action_plans_by_audit(self, audit_uuid):
|
||||
"""Deletes an action plan having the specified UUID
|
||||
|
||||
:param audit_uuid: The unique identifier of the related Audit
|
||||
"""
|
||||
|
||||
action_plans = self.list_action_plans(audit_uuid=audit_uuid)[1]
|
||||
|
||||
for action_plan in action_plans:
|
||||
self.delete_action_plan(action_plan['uuid'])
|
||||
|
||||
@base.handle_errors
|
||||
def update_action_plan(self, action_plan_uuid, patch):
|
||||
"""Update the specified action plan
|
||||
|
||||
:param action_plan_uuid: The unique identifier of the action_plan
|
||||
:param patch: List of dicts representing json patches.
|
||||
:return: Tuple with the server response and the updated action_plan
|
||||
"""
|
||||
|
||||
return self._patch_request('/action_plans', action_plan_uuid, patch)
|
||||
|
||||
@base.handle_errors
|
||||
def start_action_plan(self, action_plan_uuid):
|
||||
"""Start the specified action plan
|
||||
|
||||
:param action_plan_uuid: The unique identifier of the action_plan
|
||||
:return: Tuple with the server response and the updated action_plan
|
||||
"""
|
||||
|
||||
return self._patch_request(
|
||||
'/action_plans', action_plan_uuid,
|
||||
[{'path': '/state', 'op': 'replace', 'value': 'PENDING'}])
|
||||
|
||||
# ### GOALS ### #
|
||||
|
||||
@base.handle_errors
|
||||
def list_goals(self, **kwargs):
|
||||
"""List all existing goals"""
|
||||
return self._list_request('/goals', **kwargs)
|
||||
|
||||
@base.handle_errors
|
||||
def list_goals_detail(self, **kwargs):
|
||||
"""Lists details of all existing goals"""
|
||||
return self._list_request('/goals/detail', **kwargs)
|
||||
|
||||
@base.handle_errors
|
||||
def show_goal(self, goal):
|
||||
"""Gets a specific goal
|
||||
|
||||
:param goal: UUID or Name of the goal
|
||||
:return: Serialized goal as a dictionary
|
||||
"""
|
||||
return self._show_request('/goals', goal)
|
||||
|
||||
# ### ACTIONS ### #
|
||||
|
||||
@base.handle_errors
|
||||
def list_actions(self, **kwargs):
|
||||
"""List all existing actions"""
|
||||
return self._list_request('/actions', **kwargs)
|
||||
|
||||
@base.handle_errors
|
||||
def list_actions_detail(self, **kwargs):
|
||||
"""Lists details of all existing actions"""
|
||||
return self._list_request('/actions/detail', **kwargs)
|
||||
|
||||
@base.handle_errors
|
||||
def show_action(self, action_uuid):
|
||||
"""Gets a specific action
|
||||
|
||||
:param action_uuid: Unique identifier of the action
|
||||
:return: Serialized action as a dictionary
|
||||
"""
|
||||
return self._show_request('/actions', action_uuid)
|
||||
|
||||
# ### STRATEGIES ### #
|
||||
|
||||
@base.handle_errors
|
||||
def list_strategies(self, **kwargs):
|
||||
"""List all existing strategies"""
|
||||
return self._list_request('/strategies', **kwargs)
|
||||
|
||||
@base.handle_errors
|
||||
def list_strategies_detail(self, **kwargs):
|
||||
"""Lists details of all existing strategies"""
|
||||
return self._list_request('/strategies/detail', **kwargs)
|
||||
|
||||
@base.handle_errors
|
||||
def show_strategy(self, strategy):
|
||||
"""Gets a specific strategy
|
||||
|
||||
:param strategy_id: Name of the strategy
|
||||
:return: Serialized strategy as a dictionary
|
||||
"""
|
||||
return self._show_request('/strategies', strategy)
|
||||
|
||||
# ### SCORING ENGINE ### #
|
||||
|
||||
@base.handle_errors
|
||||
def list_scoring_engines(self, **kwargs):
|
||||
"""List all existing scoring_engines"""
|
||||
return self._list_request('/scoring_engines', **kwargs)
|
||||
|
||||
@base.handle_errors
|
||||
def list_scoring_engines_detail(self, **kwargs):
|
||||
"""Lists details of all existing scoring_engines"""
|
||||
return self._list_request('/scoring_engines/detail', **kwargs)
|
||||
|
||||
@base.handle_errors
|
||||
def show_scoring_engine(self, scoring_engine):
|
||||
"""Gets a specific scoring_engine
|
||||
|
||||
:param scoring_engine: UUID or Name of the scoring_engine
|
||||
:return: Serialized scoring_engine as a dictionary
|
||||
"""
|
||||
return self._show_request('/scoring_engines', scoring_engine)
|
||||
|
||||
# ### SERVICES ### #
|
||||
|
||||
@base.handle_errors
|
||||
def list_services(self, **kwargs):
|
||||
"""List all existing services"""
|
||||
return self._list_request('/services', **kwargs)
|
||||
|
||||
@base.handle_errors
|
||||
def list_services_detail(self, **kwargs):
|
||||
"""Lists details of all existing services"""
|
||||
return self._list_request('/services/detail', **kwargs)
|
||||
|
||||
@base.handle_errors
|
||||
def show_service(self, service):
|
||||
"""Gets a specific service
|
||||
|
||||
:param service: Name of the strategy
|
||||
:return: Serialized strategy as a dictionary
|
||||
"""
|
||||
return self._show_request('/services', service)
|
@ -1,259 +0,0 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2016 b<>com
|
||||
#
|
||||
# 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 functools
|
||||
|
||||
from tempest.lib.common.utils import data_utils
|
||||
from tempest.lib.common.utils import test_utils
|
||||
from tempest import test
|
||||
|
||||
from watcher_tempest_plugin import infra_optim_clients as clients
|
||||
|
||||
|
||||
class BaseInfraOptimTest(test.BaseTestCase):
|
||||
"""Base class for Infrastructure Optimization API tests."""
|
||||
|
||||
# States where the object is waiting for some event to perform a transition
|
||||
IDLE_STATES = ('RECOMMENDED',
|
||||
'FAILED',
|
||||
'SUCCEEDED',
|
||||
'CANCELLED',
|
||||
'SUSPENDED')
|
||||
# States where the object can only be DELETED (end of its life-cycle)
|
||||
FINISHED_STATES = ('FAILED',
|
||||
'SUCCEEDED',
|
||||
'CANCELLED',
|
||||
'SUPERSEDED')
|
||||
|
||||
@classmethod
|
||||
def setup_credentials(cls):
|
||||
super(BaseInfraOptimTest, cls).setup_credentials()
|
||||
cls.mgr = clients.AdminManager()
|
||||
|
||||
@classmethod
|
||||
def setup_clients(cls):
|
||||
super(BaseInfraOptimTest, cls).setup_clients()
|
||||
cls.client = cls.mgr.io_client
|
||||
|
||||
@classmethod
|
||||
def resource_setup(cls):
|
||||
super(BaseInfraOptimTest, cls).resource_setup()
|
||||
|
||||
# Set of all created audit templates UUIDs
|
||||
cls.created_audit_templates = set()
|
||||
# Set of all created audit UUIDs
|
||||
cls.created_audits = set()
|
||||
# Set of all created audit UUIDs. We use it to build the list of
|
||||
# action plans to delete (including potential orphan one(s))
|
||||
cls.created_action_plans_audit_uuids = set()
|
||||
|
||||
@classmethod
|
||||
def resource_cleanup(cls):
|
||||
"""Ensure that all created objects get destroyed."""
|
||||
try:
|
||||
action_plans_to_be_deleted = set()
|
||||
# Phase 1: Make sure all objects are in an idle state
|
||||
for audit_uuid in cls.created_audits:
|
||||
test_utils.call_until_true(
|
||||
func=functools.partial(
|
||||
cls.is_audit_idle, audit_uuid),
|
||||
duration=30,
|
||||
sleep_for=.5
|
||||
)
|
||||
|
||||
for audit_uuid in cls.created_action_plans_audit_uuids:
|
||||
_, action_plans = cls.client.list_action_plans(
|
||||
audit_uuid=audit_uuid)
|
||||
action_plans_to_be_deleted.update(
|
||||
ap['uuid'] for ap in action_plans['action_plans'])
|
||||
|
||||
for action_plan in action_plans['action_plans']:
|
||||
try:
|
||||
test_utils.call_until_true(
|
||||
func=functools.partial(
|
||||
cls.is_action_plan_idle, action_plan['uuid']),
|
||||
duration=30,
|
||||
sleep_for=.5
|
||||
)
|
||||
except Exception:
|
||||
action_plans_to_be_deleted.remove(
|
||||
action_plan['uuid'])
|
||||
|
||||
# Phase 2: Delete them all
|
||||
for action_plan_uuid in action_plans_to_be_deleted:
|
||||
cls.delete_action_plan(action_plan_uuid)
|
||||
|
||||
for audit_uuid in cls.created_audits.copy():
|
||||
cls.delete_audit(audit_uuid)
|
||||
|
||||
for audit_template_uuid in cls.created_audit_templates.copy():
|
||||
cls.delete_audit_template(audit_template_uuid)
|
||||
|
||||
finally:
|
||||
super(BaseInfraOptimTest, cls).resource_cleanup()
|
||||
|
||||
def validate_self_link(self, resource, uuid, link):
|
||||
"""Check whether the given self link formatted correctly."""
|
||||
expected_link = "{base}/{pref}/{res}/{uuid}".format(
|
||||
base=self.client.base_url,
|
||||
pref=self.client.URI_PREFIX,
|
||||
res=resource,
|
||||
uuid=uuid
|
||||
)
|
||||
self.assertEqual(expected_link, link)
|
||||
|
||||
def assert_expected(self, expected, actual,
|
||||
keys=('created_at', 'updated_at', 'deleted_at')):
|
||||
# Check if not expected keys/values exists in actual response body
|
||||
for key, value in expected.items():
|
||||
if key not in keys:
|
||||
self.assertIn(key, actual)
|
||||
self.assertEqual(value, actual[key])
|
||||
|
||||
# ### AUDIT TEMPLATES ### #
|
||||
|
||||
@classmethod
|
||||
def create_audit_template(cls, goal, name=None, description=None,
|
||||
strategy=None, scope=None):
|
||||
"""Wrapper utility for creating a test audit template
|
||||
|
||||
:param goal: Goal UUID or name related to the audit template.
|
||||
:param name: The name of the audit template. Default: My Audit Template
|
||||
:param description: The description of the audit template.
|
||||
:param strategy: Strategy UUID or name related to the audit template.
|
||||
:param scope: Scope that will be applied on all derived audits.
|
||||
:return: A tuple with The HTTP response and its body
|
||||
"""
|
||||
description = description or data_utils.rand_name(
|
||||
'test-audit_template')
|
||||
resp, body = cls.client.create_audit_template(
|
||||
name=name, description=description,
|
||||
goal=goal, strategy=strategy, scope=scope)
|
||||
|
||||
cls.created_audit_templates.add(body['uuid'])
|
||||
|
||||
return resp, body
|
||||
|
||||
@classmethod
|
||||
def delete_audit_template(cls, uuid):
|
||||
"""Deletes a audit_template having the specified UUID
|
||||
|
||||
:param uuid: The unique identifier of the audit template
|
||||
:return: Server response
|
||||
"""
|
||||
resp, _ = cls.client.delete_audit_template(uuid)
|
||||
|
||||
if uuid in cls.created_audit_templates:
|
||||
cls.created_audit_templates.remove(uuid)
|
||||
|
||||
return resp
|
||||
|
||||
# ### AUDITS ### #
|
||||
|
||||
@classmethod
|
||||
def create_audit(cls, audit_template_uuid, audit_type='ONESHOT',
|
||||
state=None, interval=None, parameters=None):
|
||||
"""Wrapper utility for creating a test audit
|
||||
|
||||
:param audit_template_uuid: Audit Template UUID this audit will use
|
||||
:param audit_type: Audit type (either ONESHOT or CONTINUOUS)
|
||||
:param state: Audit state (str)
|
||||
:param interval: Audit interval in seconds or cron syntax (str)
|
||||
:param parameters: list of execution parameters
|
||||
:return: A tuple with The HTTP response and its body
|
||||
"""
|
||||
resp, body = cls.client.create_audit(
|
||||
audit_template_uuid=audit_template_uuid, audit_type=audit_type,
|
||||
state=state, interval=interval, parameters=parameters)
|
||||
|
||||
cls.created_audits.add(body['uuid'])
|
||||
cls.created_action_plans_audit_uuids.add(body['uuid'])
|
||||
|
||||
return resp, body
|
||||
|
||||
@classmethod
|
||||
def delete_audit(cls, audit_uuid):
|
||||
"""Deletes an audit having the specified UUID
|
||||
|
||||
:param audit_uuid: The unique identifier of the audit.
|
||||
:return: the HTTP response
|
||||
"""
|
||||
resp, _ = cls.client.delete_audit(audit_uuid)
|
||||
|
||||
if audit_uuid in cls.created_audits:
|
||||
cls.created_audits.remove(audit_uuid)
|
||||
|
||||
return resp
|
||||
|
||||
@classmethod
|
||||
def has_audit_succeeded(cls, audit_uuid):
|
||||
_, audit = cls.client.show_audit(audit_uuid)
|
||||
return audit.get('state') == 'SUCCEEDED'
|
||||
|
||||
@classmethod
|
||||
def has_audit_finished(cls, audit_uuid):
|
||||
_, audit = cls.client.show_audit(audit_uuid)
|
||||
return audit.get('state') in cls.FINISHED_STATES
|
||||
|
||||
@classmethod
|
||||
def is_audit_idle(cls, audit_uuid):
|
||||
_, audit = cls.client.show_audit(audit_uuid)
|
||||
return audit.get('state') in cls.IDLE_STATES
|
||||
|
||||
# ### ACTION PLANS ### #
|
||||
|
||||
@classmethod
|
||||
def create_action_plan(cls, audit_template_uuid, **audit_kwargs):
|
||||
"""Wrapper utility for creating a test action plan
|
||||
|
||||
:param audit_template_uuid: Audit template UUID to use
|
||||
:param audit_kwargs: Dict of audit properties to set
|
||||
:return: The action plan as dict
|
||||
"""
|
||||
_, audit = cls.create_audit(audit_template_uuid, **audit_kwargs)
|
||||
audit_uuid = audit['uuid']
|
||||
|
||||
assert test_utils.call_until_true(
|
||||
func=functools.partial(cls.has_audit_finished, audit_uuid),
|
||||
duration=30,
|
||||
sleep_for=.5
|
||||
)
|
||||
|
||||
_, action_plans = cls.client.list_action_plans(audit_uuid=audit_uuid)
|
||||
if len(action_plans['action_plans']) == 0:
|
||||
return
|
||||
|
||||
return action_plans['action_plans'][0]
|
||||
|
||||
@classmethod
|
||||
def delete_action_plan(cls, action_plan_uuid):
|
||||
"""Deletes an action plan having the specified UUID
|
||||
|
||||
:param action_plan_uuid: The unique identifier of the action plan.
|
||||
:return: the HTTP response
|
||||
"""
|
||||
resp, _ = cls.client.delete_action_plan(action_plan_uuid)
|
||||
|
||||
if action_plan_uuid in cls.created_action_plans_audit_uuids:
|
||||
cls.created_action_plans_audit_uuids.remove(action_plan_uuid)
|
||||
|
||||
return resp
|
||||
|
||||
@classmethod
|
||||
def is_action_plan_idle(cls, action_plan_uuid):
|
||||
"""This guard makes sure your action plan is not running"""
|
||||
_, action_plan = cls.client.show_action_plan(action_plan_uuid)
|
||||
return action_plan.get('state') in cls.IDLE_STATES
|
@ -1,110 +0,0 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2016 b<>com
|
||||
#
|
||||
# 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 __future__ import unicode_literals
|
||||
|
||||
import collections
|
||||
import functools
|
||||
|
||||
from tempest.lib.common.utils import test_utils
|
||||
from tempest.lib import decorators
|
||||
|
||||
from watcher_tempest_plugin.tests.api.admin import base
|
||||
|
||||
|
||||
class TestShowListAction(base.BaseInfraOptimTest):
|
||||
"""Tests for actions"""
|
||||
|
||||
@classmethod
|
||||
def resource_setup(cls):
|
||||
super(TestShowListAction, cls).resource_setup()
|
||||
_, cls.goal = cls.client.show_goal("DUMMY")
|
||||
_, cls.audit_template = cls.create_audit_template(cls.goal['uuid'])
|
||||
_, cls.audit = cls.create_audit(cls.audit_template['uuid'])
|
||||
|
||||
assert test_utils.call_until_true(
|
||||
func=functools.partial(cls.has_audit_finished, cls.audit['uuid']),
|
||||
duration=30,
|
||||
sleep_for=.5
|
||||
)
|
||||
_, action_plans = cls.client.list_action_plans(
|
||||
audit_uuid=cls.audit['uuid'])
|
||||
cls.action_plan = action_plans['action_plans'][0]
|
||||
|
||||
@decorators.attr(type='smoke')
|
||||
def test_show_one_action(self):
|
||||
_, body = self.client.list_actions(
|
||||
action_plan_uuid=self.action_plan["uuid"])
|
||||
actions = body['actions']
|
||||
|
||||
_, action = self.client.show_action(actions[0]["uuid"])
|
||||
|
||||
self.assertEqual(self.action_plan["uuid"], action['action_plan_uuid'])
|
||||
self.assertEqual("PENDING", action['state'])
|
||||
|
||||
@decorators.attr(type='smoke')
|
||||
def test_show_action_with_links(self):
|
||||
_, body = self.client.list_actions(
|
||||
action_plan_uuid=self.action_plan["uuid"])
|
||||
actions = body['actions']
|
||||
|
||||
_, action = self.client.show_action(actions[0]["uuid"])
|
||||
|
||||
self.assertIn('links', action.keys())
|
||||
self.assertEqual(2, len(action['links']))
|
||||
self.assertIn(action['uuid'], action['links'][0]['href'])
|
||||
|
||||
@decorators.attr(type="smoke")
|
||||
def test_list_actions(self):
|
||||
_, body = self.client.list_actions()
|
||||
|
||||
# Verify self links.
|
||||
for action in body['actions']:
|
||||
self.validate_self_link('actions', action['uuid'],
|
||||
action['links'][0]['href'])
|
||||
|
||||
@decorators.attr(type="smoke")
|
||||
def test_list_actions_by_action_plan(self):
|
||||
_, body = self.client.list_actions(
|
||||
action_plan_uuid=self.action_plan["uuid"])
|
||||
|
||||
for item in body['actions']:
|
||||
self.assertEqual(self.action_plan["uuid"],
|
||||
item['action_plan_uuid'])
|
||||
|
||||
action_counter = collections.Counter(
|
||||
act['action_type'] for act in body['actions'])
|
||||
|
||||
# A dummy strategy generates 2 "nop" actions and 1 "sleep" action
|
||||
self.assertEqual(3, len(body['actions']))
|
||||
self.assertEqual(2, action_counter.get("nop"))
|
||||
self.assertEqual(1, action_counter.get("sleep"))
|
||||
|
||||
@decorators.attr(type="smoke")
|
||||
def test_list_actions_by_audit(self):
|
||||
_, body = self.client.list_actions(audit_uuid=self.audit["uuid"])
|
||||
|
||||
for item in body['actions']:
|
||||
self.assertEqual(self.action_plan["uuid"],
|
||||
item['action_plan_uuid'])
|
||||
|
||||
action_counter = collections.Counter(
|
||||
act['action_type'] for act in body['actions'])
|
||||
|
||||
# A dummy strategy generates 2 "nop" actions and 1 "sleep" action
|
||||
self.assertEqual(3, len(body['actions']))
|
||||
self.assertEqual(2, action_counter.get("nop"))
|
||||
self.assertEqual(1, action_counter.get("sleep"))
|
@ -1,140 +0,0 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2016 b<>com
|
||||
#
|
||||
# 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 __future__ import unicode_literals
|
||||
|
||||
import functools
|
||||
|
||||
from tempest.lib.common.utils import test_utils
|
||||
from tempest.lib import decorators
|
||||
from tempest.lib import exceptions
|
||||
|
||||
from watcher_tempest_plugin.tests.api.admin import base
|
||||
|
||||
|
||||
class TestCreateDeleteExecuteActionPlan(base.BaseInfraOptimTest):
|
||||
"""Tests for action plans"""
|
||||
|
||||
@decorators.attr(type='smoke')
|
||||
def test_create_action_plan(self):
|
||||
_, goal = self.client.show_goal("dummy")
|
||||
_, audit_template = self.create_audit_template(goal['uuid'])
|
||||
_, audit = self.create_audit(audit_template['uuid'])
|
||||
|
||||
self.assertTrue(test_utils.call_until_true(
|
||||
func=functools.partial(self.has_audit_finished, audit['uuid']),
|
||||
duration=30,
|
||||
sleep_for=.5
|
||||
))
|
||||
_, action_plans = self.client.list_action_plans(
|
||||
audit_uuid=audit['uuid'])
|
||||
action_plan = action_plans['action_plans'][0]
|
||||
|
||||
_, action_plan = self.client.show_action_plan(action_plan['uuid'])
|
||||
|
||||
self.assertEqual(audit['uuid'], action_plan['audit_uuid'])
|
||||
self.assertEqual('RECOMMENDED', action_plan['state'])
|
||||
|
||||
@decorators.attr(type='smoke')
|
||||
def test_delete_action_plan(self):
|
||||
_, goal = self.client.show_goal("dummy")
|
||||
_, audit_template = self.create_audit_template(goal['uuid'])
|
||||
_, audit = self.create_audit(audit_template['uuid'])
|
||||
|
||||
self.assertTrue(test_utils.call_until_true(
|
||||
func=functools.partial(self.has_audit_finished, audit['uuid']),
|
||||
duration=30,
|
||||
sleep_for=.5
|
||||
))
|
||||
_, action_plans = self.client.list_action_plans(
|
||||
audit_uuid=audit['uuid'])
|
||||
action_plan = action_plans['action_plans'][0]
|
||||
|
||||
_, action_plan = self.client.show_action_plan(action_plan['uuid'])
|
||||
|
||||
self.client.delete_action_plan(action_plan['uuid'])
|
||||
|
||||
self.assertRaises(exceptions.NotFound, self.client.show_action_plan,
|
||||
action_plan['uuid'])
|
||||
|
||||
|
||||
class TestShowListActionPlan(base.BaseInfraOptimTest):
|
||||
"""Tests for action_plan."""
|
||||
|
||||
@classmethod
|
||||
def resource_setup(cls):
|
||||
super(TestShowListActionPlan, cls).resource_setup()
|
||||
_, cls.goal = cls.client.show_goal("dummy")
|
||||
_, cls.audit_template = cls.create_audit_template(cls.goal['uuid'])
|
||||
_, cls.audit = cls.create_audit(cls.audit_template['uuid'])
|
||||
|
||||
assert test_utils.call_until_true(
|
||||
func=functools.partial(cls.has_audit_finished, cls.audit['uuid']),
|
||||
duration=30,
|
||||
sleep_for=.5
|
||||
)
|
||||
_, action_plans = cls.client.list_action_plans(
|
||||
audit_uuid=cls.audit['uuid'])
|
||||
if len(action_plans['action_plans']) > 0:
|
||||
cls.action_plan = action_plans['action_plans'][0]
|
||||
|
||||
@decorators.attr(type='smoke')
|
||||
def test_show_action_plan(self):
|
||||
_, action_plan = self.client.show_action_plan(
|
||||
self.action_plan['uuid'])
|
||||
|
||||
self.assert_expected(self.action_plan, action_plan)
|
||||
|
||||
@decorators.attr(type='smoke')
|
||||
def test_show_action_plan_detail(self):
|
||||
_, action_plans = self.client.list_action_plans_detail(
|
||||
audit_uuid=self.audit['uuid'])
|
||||
|
||||
action_plan = action_plans['action_plans'][0]
|
||||
|
||||
self.assert_expected(self.action_plan, action_plan)
|
||||
|
||||
@decorators.attr(type='smoke')
|
||||
def test_show_action_plan_with_links(self):
|
||||
_, action_plan = self.client.show_action_plan(
|
||||
self.action_plan['uuid'])
|
||||
self.assertIn('links', action_plan.keys())
|
||||
self.assertEqual(2, len(action_plan['links']))
|
||||
self.assertIn(action_plan['uuid'],
|
||||
action_plan['links'][0]['href'])
|
||||
|
||||
@decorators.attr(type="smoke")
|
||||
def test_list_action_plans(self):
|
||||
_, body = self.client.list_action_plans()
|
||||
self.assertIn(self.action_plan['uuid'],
|
||||
[i['uuid'] for i in body['action_plans']])
|
||||
# Verify self links.
|
||||
for action_plan in body['action_plans']:
|
||||
self.validate_self_link('action_plans', action_plan['uuid'],
|
||||
action_plan['links'][0]['href'])
|
||||
|
||||
@decorators.attr(type='smoke')
|
||||
def test_list_with_limit(self):
|
||||
# We create 3 extra audits to exceed the limit we fix
|
||||
for _ in range(3):
|
||||
self.create_action_plan(self.audit_template['uuid'])
|
||||
|
||||
_, body = self.client.list_action_plans(limit=3)
|
||||
|
||||
next_marker = body['action_plans'][-1]['uuid']
|
||||
|
||||
self.assertEqual(3, len(body['action_plans']))
|
||||
self.assertIn(next_marker, body['next'])
|
@ -1,47 +0,0 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2016 b<>com
|
||||
#
|
||||
# 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 tempest.lib import decorators
|
||||
|
||||
from watcher_tempest_plugin.tests.api.admin import base
|
||||
|
||||
|
||||
class TestApiDiscovery(base.BaseInfraOptimTest):
|
||||
"""Tests for API discovery features."""
|
||||
|
||||
@decorators.attr(type='smoke')
|
||||
def test_api_versions(self):
|
||||
_, descr = self.client.get_api_description()
|
||||
expected_versions = ('v1',)
|
||||
versions = [version['id'] for version in descr['versions']]
|
||||
|
||||
for v in expected_versions:
|
||||
self.assertIn(v, versions)
|
||||
|
||||
@decorators.attr(type='smoke')
|
||||
def test_default_version(self):
|
||||
_, descr = self.client.get_api_description()
|
||||
default_version = descr['default_version']
|
||||
self.assertEqual('v1', default_version['id'])
|
||||
|
||||
@decorators.attr(type='smoke')
|
||||
def test_version_1_resources(self):
|
||||
_, descr = self.client.get_version_description(version='v1')
|
||||
expected_resources = ('audit_templates', 'audits', 'action_plans',
|
||||
'actions', 'links', 'media_types')
|
||||
|
||||
for res in expected_resources:
|
||||
self.assertIn(res, descr)
|
@ -1,221 +0,0 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2016 b<>com
|
||||
#
|
||||
# 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 __future__ import unicode_literals
|
||||
|
||||
import functools
|
||||
|
||||
from tempest.lib.common.utils import test_utils
|
||||
from tempest.lib import decorators
|
||||
from tempest.lib import exceptions
|
||||
|
||||
from watcher_tempest_plugin.tests.api.admin import base
|
||||
|
||||
|
||||
class TestCreateUpdateDeleteAudit(base.BaseInfraOptimTest):
|
||||
"""Tests for audit."""
|
||||
|
||||
audit_states = ['ONGOING', 'SUCCEEDED', 'FAILED',
|
||||
'CANCELLED', 'DELETED', 'PENDING', 'SUSPENDED']
|
||||
|
||||
def assert_expected(self, expected, actual,
|
||||
keys=('created_at', 'updated_at',
|
||||
'deleted_at', 'state')):
|
||||
super(TestCreateUpdateDeleteAudit, self).assert_expected(
|
||||
expected, actual, keys)
|
||||
|
||||
@decorators.attr(type='smoke')
|
||||
def test_create_audit_oneshot(self):
|
||||
_, goal = self.client.show_goal("dummy")
|
||||
_, audit_template = self.create_audit_template(goal['uuid'])
|
||||
|
||||
audit_params = dict(
|
||||
audit_template_uuid=audit_template['uuid'],
|
||||
audit_type='ONESHOT',
|
||||
)
|
||||
|
||||
_, body = self.create_audit(**audit_params)
|
||||
audit_params.pop('audit_template_uuid')
|
||||
audit_params['goal_uuid'] = goal['uuid']
|
||||
self.assert_expected(audit_params, body)
|
||||
|
||||
_, audit = self.client.show_audit(body['uuid'])
|
||||
self.assert_expected(audit, body)
|
||||
|
||||
@decorators.attr(type='smoke')
|
||||
def test_create_audit_continuous(self):
|
||||
_, goal = self.client.show_goal("dummy")
|
||||
_, audit_template = self.create_audit_template(goal['uuid'])
|
||||
|
||||
audit_params = dict(
|
||||
audit_template_uuid=audit_template['uuid'],
|
||||
audit_type='CONTINUOUS',
|
||||
interval='7200',
|
||||
)
|
||||
|
||||
_, body = self.create_audit(**audit_params)
|
||||
audit_params.pop('audit_template_uuid')
|
||||
audit_params['goal_uuid'] = goal['uuid']
|
||||
self.assert_expected(audit_params, body)
|
||||
|
||||
_, audit = self.client.show_audit(body['uuid'])
|
||||
self.assert_expected(audit, body)
|
||||
|
||||
@decorators.attr(type='smoke')
|
||||
def test_create_audit_with_wrong_audit_template(self):
|
||||
audit_params = dict(
|
||||
audit_template_uuid='INVALID',
|
||||
audit_type='ONESHOT',
|
||||
)
|
||||
|
||||
self.assertRaises(
|
||||
exceptions.BadRequest, self.create_audit, **audit_params)
|
||||
|
||||
@decorators.attr(type='smoke')
|
||||
def test_create_audit_with_invalid_state(self):
|
||||
_, goal = self.client.show_goal("dummy")
|
||||
_, audit_template = self.create_audit_template(goal['uuid'])
|
||||
|
||||
audit_params = dict(
|
||||
audit_template_uuid=audit_template['uuid'],
|
||||
state='INVALID',
|
||||
)
|
||||
|
||||
self.assertRaises(
|
||||
exceptions.BadRequest, self.create_audit, **audit_params)
|
||||
|
||||
@decorators.attr(type='smoke')
|
||||
def test_create_audit_with_no_state(self):
|
||||
_, goal = self.client.show_goal("dummy")
|
||||
_, audit_template = self.create_audit_template(goal['uuid'])
|
||||
|
||||
audit_params = dict(
|
||||
audit_template_uuid=audit_template['uuid'],
|
||||
state='',
|
||||
)
|
||||
|
||||
_, body = self.create_audit(**audit_params)
|
||||
audit_params.pop('audit_template_uuid')
|
||||
audit_params['goal_uuid'] = goal['uuid']
|
||||
self.assert_expected(audit_params, body)
|
||||
|
||||
_, audit = self.client.show_audit(body['uuid'])
|
||||
|
||||
initial_audit_state = audit.pop('state')
|
||||
self.assertIn(initial_audit_state, self.audit_states)
|
||||
|
||||
self.assert_expected(audit, body)
|
||||
|
||||
@decorators.attr(type='smoke')
|
||||
def test_delete_audit(self):
|
||||
_, goal = self.client.show_goal("dummy")
|
||||
_, audit_template = self.create_audit_template(goal['uuid'])
|
||||
_, body = self.create_audit(audit_template['uuid'])
|
||||
audit_uuid = body['uuid']
|
||||
|
||||
test_utils.call_until_true(
|
||||
func=functools.partial(
|
||||
self.is_audit_idle, audit_uuid),
|
||||
duration=10,
|
||||
sleep_for=.5
|
||||
)
|
||||
|
||||
def is_audit_deleted(uuid):
|
||||
try:
|
||||
return not bool(self.client.show_audit(uuid))
|
||||
except exceptions.NotFound:
|
||||
return True
|
||||
|
||||
self.delete_audit(audit_uuid)
|
||||
|
||||
test_utils.call_until_true(
|
||||
func=functools.partial(is_audit_deleted, audit_uuid),
|
||||
duration=5,
|
||||
sleep_for=1
|
||||
)
|
||||
|
||||
self.assertTrue(is_audit_deleted(audit_uuid))
|
||||
|
||||
|
||||
class TestShowListAudit(base.BaseInfraOptimTest):
|
||||
"""Tests for audit."""
|
||||
|
||||
audit_states = ['ONGOING', 'SUCCEEDED', 'FAILED',
|
||||
'CANCELLED', 'DELETED', 'PENDING', 'SUSPENDED']
|
||||
|
||||
@classmethod
|
||||
def resource_setup(cls):
|
||||
super(TestShowListAudit, cls).resource_setup()
|
||||
_, cls.goal = cls.client.show_goal("dummy")
|
||||
_, cls.audit_template = cls.create_audit_template(cls.goal['uuid'])
|
||||
_, cls.audit = cls.create_audit(cls.audit_template['uuid'])
|
||||
|
||||
def assert_expected(self, expected, actual,
|
||||
keys=('created_at', 'updated_at',
|
||||
'deleted_at', 'state')):
|
||||
super(TestShowListAudit, self).assert_expected(
|
||||
expected, actual, keys)
|
||||
|
||||
@decorators.attr(type='smoke')
|
||||
def test_show_audit(self):
|
||||
_, audit = self.client.show_audit(
|
||||
self.audit['uuid'])
|
||||
|
||||
initial_audit = self.audit.copy()
|
||||
del initial_audit['state']
|
||||
audit_state = audit['state']
|
||||
actual_audit = audit.copy()
|
||||
del actual_audit['state']
|
||||
|
||||
self.assertIn(audit_state, self.audit_states)
|
||||
self.assert_expected(initial_audit, actual_audit)
|
||||
|
||||
@decorators.attr(type='smoke')
|
||||
def test_show_audit_with_links(self):
|
||||
_, audit = self.client.show_audit(
|
||||
self.audit['uuid'])
|
||||
self.assertIn('links', audit.keys())
|
||||
self.assertEqual(2, len(audit['links']))
|
||||
self.assertIn(audit['uuid'],
|
||||
audit['links'][0]['href'])
|
||||
|
||||
@decorators.attr(type="smoke")
|
||||
def test_list_audits(self):
|
||||
_, body = self.client.list_audits()
|
||||
self.assertIn(self.audit['uuid'],
|
||||
[i['uuid'] for i in body['audits']])
|
||||
# Verify self links.
|
||||
for audit in body['audits']:
|
||||
self.validate_self_link('audits', audit['uuid'],
|
||||
audit['links'][0]['href'])
|
||||
|
||||
@decorators.attr(type='smoke')
|
||||
def test_list_with_limit(self):
|
||||
# We create 3 extra audits to exceed the limit we fix
|
||||
for _ in range(3):
|
||||
self.create_audit(self.audit_template['uuid'])
|
||||
|
||||
_, body = self.client.list_audits(limit=3)
|
||||
|
||||
next_marker = body['audits'][-1]['uuid']
|
||||
self.assertEqual(3, len(body['audits']))
|
||||
self.assertIn(next_marker, body['next'])
|
||||
|
||||
@decorators.attr(type='smoke')
|
||||
def test_list_audits_related_to_given_audit_template(self):
|
||||
_, body = self.client.list_audits(
|
||||
goal=self.goal['uuid'])
|
||||
self.assertIn(self.audit['uuid'], [n['uuid'] for n in body['audits']])
|
@ -1,226 +0,0 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2016 b<>com
|
||||
#
|
||||
# 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 __future__ import unicode_literals
|
||||
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from tempest.lib import decorators
|
||||
from tempest.lib import exceptions
|
||||
|
||||
from watcher_tempest_plugin.tests.api.admin import base
|
||||
|
||||
|
||||
class TestCreateDeleteAuditTemplate(base.BaseInfraOptimTest):
|
||||
"""Tests on audit templates"""
|
||||
|
||||
@decorators.attr(type='smoke')
|
||||
def test_create_audit_template(self):
|
||||
goal_name = "dummy"
|
||||
_, goal = self.client.show_goal(goal_name)
|
||||
|
||||
params = {
|
||||
'name': 'my at name %s' % uuidutils.generate_uuid(),
|
||||
'description': 'my at description',
|
||||
'goal': goal['uuid']}
|
||||
expected_data = {
|
||||
'name': params['name'],
|
||||
'description': params['description'],
|
||||
'goal_uuid': params['goal'],
|
||||
'goal_name': goal_name,
|
||||
'strategy_uuid': None,
|
||||
'strategy_name': None}
|
||||
|
||||
_, body = self.create_audit_template(**params)
|
||||
self.assert_expected(expected_data, body)
|
||||
|
||||
_, audit_template = self.client.show_audit_template(body['uuid'])
|
||||
self.assert_expected(audit_template, body)
|
||||
|
||||
@decorators.attr(type='smoke')
|
||||
def test_create_audit_template_unicode_description(self):
|
||||
goal_name = "dummy"
|
||||
_, goal = self.client.show_goal(goal_name)
|
||||
# Use a unicode string for testing:
|
||||
params = {
|
||||
'name': 'my at name %s' % uuidutils.generate_uuid(),
|
||||
'description': 'my àt déscrïptïôn',
|
||||
'goal': goal['uuid']}
|
||||
|
||||
expected_data = {
|
||||
'name': params['name'],
|
||||
'description': params['description'],
|
||||
'goal_uuid': params['goal'],
|
||||
'goal_name': goal_name,
|
||||
'strategy_uuid': None,
|
||||
'strategy_name': None}
|
||||
|
||||
_, body = self.create_audit_template(**params)
|
||||
self.assert_expected(expected_data, body)
|
||||
|
||||
_, audit_template = self.client.show_audit_template(body['uuid'])
|
||||
self.assert_expected(audit_template, body)
|
||||
|
||||
@decorators.attr(type='smoke')
|
||||
def test_delete_audit_template(self):
|
||||
_, goal = self.client.show_goal("dummy")
|
||||
_, body = self.create_audit_template(goal=goal['uuid'])
|
||||
audit_uuid = body['uuid']
|
||||
|
||||
self.delete_audit_template(audit_uuid)
|
||||
|
||||
self.assertRaises(exceptions.NotFound, self.client.show_audit_template,
|
||||
audit_uuid)
|
||||
|
||||
|
||||
class TestAuditTemplate(base.BaseInfraOptimTest):
|
||||
"""Tests for audit_template."""
|
||||
|
||||
@classmethod
|
||||
def resource_setup(cls):
|
||||
super(TestAuditTemplate, cls).resource_setup()
|
||||
_, cls.goal = cls.client.show_goal("dummy")
|
||||
_, cls.strategy = cls.client.show_strategy("dummy")
|
||||
_, cls.audit_template = cls.create_audit_template(
|
||||
goal=cls.goal['uuid'], strategy=cls.strategy['uuid'])
|
||||
|
||||
@decorators.attr(type='smoke')
|
||||
def test_show_audit_template(self):
|
||||
_, audit_template = self.client.show_audit_template(
|
||||
self.audit_template['uuid'])
|
||||
|
||||
self.assert_expected(self.audit_template, audit_template)
|
||||
|
||||
@decorators.attr(type='smoke')
|
||||
def test_filter_audit_template_by_goal_uuid(self):
|
||||
_, audit_templates = self.client.list_audit_templates(
|
||||
goal=self.audit_template['goal_uuid'])
|
||||
|
||||
audit_template_uuids = [
|
||||
at["uuid"] for at in audit_templates['audit_templates']]
|
||||
self.assertIn(self.audit_template['uuid'], audit_template_uuids)
|
||||
|
||||
@decorators.attr(type='smoke')
|
||||
def test_filter_audit_template_by_strategy_uuid(self):
|
||||
_, audit_templates = self.client.list_audit_templates(
|
||||
strategy=self.audit_template['strategy_uuid'])
|
||||
|
||||
audit_template_uuids = [
|
||||
at["uuid"] for at in audit_templates['audit_templates']]
|
||||
self.assertIn(self.audit_template['uuid'], audit_template_uuids)
|
||||
|
||||
@decorators.attr(type='smoke')
|
||||
def test_show_audit_template_with_links(self):
|
||||
_, audit_template = self.client.show_audit_template(
|
||||
self.audit_template['uuid'])
|
||||
self.assertIn('links', audit_template.keys())
|
||||
self.assertEqual(2, len(audit_template['links']))
|
||||
self.assertIn(audit_template['uuid'],
|
||||
audit_template['links'][0]['href'])
|
||||
|
||||
@decorators.attr(type="smoke")
|
||||
def test_list_audit_templates(self):
|
||||
_, body = self.client.list_audit_templates()
|
||||
self.assertIn(self.audit_template['uuid'],
|
||||
[i['uuid'] for i in body['audit_templates']])
|
||||
# Verify self links.
|
||||
for audit_template in body['audit_templates']:
|
||||
self.validate_self_link('audit_templates', audit_template['uuid'],
|
||||
audit_template['links'][0]['href'])
|
||||
|
||||
@decorators.attr(type='smoke')
|
||||
def test_list_with_limit(self):
|
||||
# We create 3 extra audit templates to exceed the limit we fix
|
||||
for _ in range(3):
|
||||
self.create_audit_template(self.goal['uuid'])
|
||||
|
||||
_, body = self.client.list_audit_templates(limit=3)
|
||||
|
||||
next_marker = body['audit_templates'][-1]['uuid']
|
||||
self.assertEqual(3, len(body['audit_templates']))
|
||||
self.assertIn(next_marker, body['next'])
|
||||
|
||||
@decorators.attr(type='smoke')
|
||||
def test_update_audit_template_replace(self):
|
||||
_, new_goal = self.client.show_goal("server_consolidation")
|
||||
_, new_strategy = self.client.show_strategy("basic")
|
||||
|
||||
params = {'name': 'my at name %s' % uuidutils.generate_uuid(),
|
||||
'description': 'my at description',
|
||||
'goal': self.goal['uuid']}
|
||||
|
||||
_, body = self.create_audit_template(**params)
|
||||
|
||||
new_name = 'my at new name %s' % uuidutils.generate_uuid()
|
||||
new_description = 'my new at description'
|
||||
|
||||
patch = [{'path': '/name',
|
||||
'op': 'replace',
|
||||
'value': new_name},
|
||||
{'path': '/description',
|
||||
'op': 'replace',
|
||||
'value': new_description},
|
||||
{'path': '/goal',
|
||||
'op': 'replace',
|
||||
'value': new_goal['uuid']},
|
||||
{'path': '/strategy',
|
||||
'op': 'replace',
|
||||
'value': new_strategy['uuid']}]
|
||||
|
||||
self.client.update_audit_template(body['uuid'], patch)
|
||||
|
||||
_, body = self.client.show_audit_template(body['uuid'])
|
||||
self.assertEqual(new_name, body['name'])
|
||||
self.assertEqual(new_description, body['description'])
|
||||
self.assertEqual(new_goal['uuid'], body['goal_uuid'])
|
||||
self.assertEqual(new_strategy['uuid'], body['strategy_uuid'])
|
||||
|
||||
@decorators.attr(type='smoke')
|
||||
def test_update_audit_template_remove(self):
|
||||
description = 'my at description'
|
||||
name = 'my at name %s' % uuidutils.generate_uuid()
|
||||
params = {'name': name,
|
||||
'description': description,
|
||||
'goal': self.goal['uuid']}
|
||||
|
||||
_, audit_template = self.create_audit_template(**params)
|
||||
|
||||
# Removing the description
|
||||
self.client.update_audit_template(
|
||||
audit_template['uuid'],
|
||||
[{'path': '/description', 'op': 'remove'}])
|
||||
|
||||
_, body = self.client.show_audit_template(audit_template['uuid'])
|
||||
self.assertIsNone(body.get('description'))
|
||||
|
||||
# Assert nothing else was changed
|
||||
self.assertEqual(name, body['name'])
|
||||
self.assertIsNone(body['description'])
|
||||
self.assertEqual(self.goal['uuid'], body['goal_uuid'])
|
||||
|
||||
@decorators.attr(type='smoke')
|
||||
def test_update_audit_template_add(self):
|
||||
params = {'name': 'my at name %s' % uuidutils.generate_uuid(),
|
||||
'goal': self.goal['uuid']}
|
||||
|
||||
_, body = self.create_audit_template(**params)
|
||||
|
||||
patch = [{'path': '/description', 'op': 'add', 'value': 'description'}]
|
||||
|
||||
self.client.update_audit_template(body['uuid'], patch)
|
||||
|
||||
_, body = self.client.show_audit_template(body['uuid'])
|
||||
self.assertEqual('description', body['description'])
|
@ -1,66 +0,0 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2016 b<>com
|
||||
#
|
||||
# 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 __future__ import unicode_literals
|
||||
|
||||
from tempest.lib import decorators
|
||||
|
||||
from watcher_tempest_plugin.tests.api.admin import base
|
||||
|
||||
|
||||
class TestShowListGoal(base.BaseInfraOptimTest):
|
||||
"""Tests for goals"""
|
||||
|
||||
DUMMY_GOAL = "dummy"
|
||||
|
||||
@classmethod
|
||||
def resource_setup(cls):
|
||||
super(TestShowListGoal, cls).resource_setup()
|
||||
|
||||
def assert_expected(self, expected, actual,
|
||||
keys=('created_at', 'updated_at', 'deleted_at')):
|
||||
super(TestShowListGoal, self).assert_expected(
|
||||
expected, actual, keys)
|
||||
|
||||
@decorators.attr(type='smoke')
|
||||
def test_show_goal(self):
|
||||
_, goal = self.client.show_goal(self.DUMMY_GOAL)
|
||||
|
||||
self.assertEqual(self.DUMMY_GOAL, goal['name'])
|
||||
expected_fields = {
|
||||
'created_at', 'deleted_at', 'display_name',
|
||||
'efficacy_specification', 'links', 'name',
|
||||
'updated_at', 'uuid'}
|
||||
self.assertEqual(expected_fields, set(goal.keys()))
|
||||
|
||||
@decorators.attr(type='smoke')
|
||||
def test_show_goal_with_links(self):
|
||||
_, goal = self.client.show_goal(self.DUMMY_GOAL)
|
||||
self.assertIn('links', goal.keys())
|
||||
self.assertEqual(2, len(goal['links']))
|
||||
self.assertIn(goal['uuid'],
|
||||
goal['links'][0]['href'])
|
||||
|
||||
@decorators.attr(type="smoke")
|
||||
def test_list_goals(self):
|
||||
_, body = self.client.list_goals()
|
||||
self.assertIn(self.DUMMY_GOAL,
|
||||
[i['name'] for i in body['goals']])
|
||||
|
||||
# Verify self links.
|
||||
for goal in body['goals']:
|
||||
self.validate_self_link('goals', goal['uuid'],
|
||||
goal['links'][0]['href'])
|
@ -1,65 +0,0 @@
|
||||
# Copyright (c) 2016 b<>com
|
||||
#
|
||||
# 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 __future__ import unicode_literals
|
||||
|
||||
from tempest.lib import decorators
|
||||
|
||||
from watcher_tempest_plugin.tests.api.admin import base
|
||||
|
||||
|
||||
class TestShowListScoringEngine(base.BaseInfraOptimTest):
|
||||
"""Tests for scoring engines"""
|
||||
|
||||
DUMMY_SCORING_ENGINE = "dummy_scorer"
|
||||
|
||||
@classmethod
|
||||
def resource_setup(cls):
|
||||
super(TestShowListScoringEngine, cls).resource_setup()
|
||||
|
||||
def assert_expected(self, expected, actual,
|
||||
keys=('created_at', 'updated_at', 'deleted_at')):
|
||||
super(TestShowListScoringEngine, self).assert_expected(
|
||||
expected, actual, keys)
|
||||
|
||||
@decorators.attr(type='smoke')
|
||||
def test_show_scoring_engine(self):
|
||||
_, scoring_engine = self.client.show_scoring_engine(
|
||||
self.DUMMY_SCORING_ENGINE)
|
||||
|
||||
self.assertEqual(self.DUMMY_SCORING_ENGINE, scoring_engine['name'])
|
||||
|
||||
expected_fields = {'metainfo', 'description', 'name', 'uuid', 'links'}
|
||||
self.assertEqual(expected_fields, set(scoring_engine.keys()))
|
||||
|
||||
@decorators.attr(type='smoke')
|
||||
def test_show_scoring_engine_with_links(self):
|
||||
_, scoring_engine = self.client.show_scoring_engine(
|
||||
self.DUMMY_SCORING_ENGINE)
|
||||
self.assertIn('links', scoring_engine.keys())
|
||||
self.assertEqual(2, len(scoring_engine['links']))
|
||||
self.assertIn(scoring_engine['uuid'],
|
||||
scoring_engine['links'][0]['href'])
|
||||
|
||||
@decorators.attr(type="smoke")
|
||||
def test_list_scoring_engines(self):
|
||||
_, body = self.client.list_scoring_engines()
|
||||
self.assertIn(self.DUMMY_SCORING_ENGINE,
|
||||
[i['name'] for i in body['scoring_engines']])
|
||||
|
||||
# Verify self links.
|
||||
for scoring_engine in body['scoring_engines']:
|
||||
self.validate_self_link('scoring_engines', scoring_engine['uuid'],
|
||||
scoring_engine['links'][0]['href'])
|
@ -1,90 +0,0 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# 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 __future__ import unicode_literals
|
||||
|
||||
from tempest.lib import decorators
|
||||
|
||||
from watcher_tempest_plugin.tests.api.admin import base
|
||||
|
||||
|
||||
class TestShowListService(base.BaseInfraOptimTest):
|
||||
"""Tests for services"""
|
||||
|
||||
DECISION_ENGINE = "watcher-decision-engine"
|
||||
APPLIER = "watcher-applier"
|
||||
|
||||
@classmethod
|
||||
def resource_setup(cls):
|
||||
super(TestShowListService, cls).resource_setup()
|
||||
|
||||
def assert_expected(self, expected, actual,
|
||||
keys=('created_at', 'updated_at', 'deleted_at')):
|
||||
super(TestShowListService, self).assert_expected(
|
||||
expected, actual, keys)
|
||||
|
||||
@decorators.attr(type='smoke')
|
||||
def test_show_service(self):
|
||||
_, body = self.client.list_services()
|
||||
self.assertIn('services', body)
|
||||
services = body['services']
|
||||
self.assertIn(self.DECISION_ENGINE,
|
||||
[i['name'] for i in body['services']])
|
||||
|
||||
service_id = filter(lambda x: self.DECISION_ENGINE == x['name'],
|
||||
services)[0]['id']
|
||||
_, service = self.client.show_service(service_id)
|
||||
|
||||
self.assertEqual(self.DECISION_ENGINE, service['name'])
|
||||
self.assertIn("host", service.keys())
|
||||
self.assertIn("last_seen_up", service.keys())
|
||||
self.assertIn("status", service.keys())
|
||||
|
||||
@decorators.attr(type='smoke')
|
||||
def test_show_service_with_links(self):
|
||||
_, body = self.client.list_services()
|
||||
self.assertIn('services', body)
|
||||
services = body['services']
|
||||
self.assertIn(self.DECISION_ENGINE,
|
||||
[i['name'] for i in body['services']])
|
||||
|
||||
service_id = filter(lambda x: self.DECISION_ENGINE == x['name'],
|
||||
services)[0]['id']
|
||||
_, service = self.client.show_service(service_id)
|
||||
|
||||
self.assertIn('links', service.keys())
|
||||
self.assertEqual(2, len(service['links']))
|
||||
self.assertIn(str(service['id']),
|
||||
service['links'][0]['href'])
|
||||
|
||||
@decorators.attr(type="smoke")
|
||||
def test_list_services(self):
|
||||
_, body = self.client.list_services()
|
||||
self.assertIn('services', body)
|
||||
services = body['services']
|
||||
self.assertIn(self.DECISION_ENGINE,
|
||||
[i['name'] for i in body['services']])
|
||||
|
||||
for service in services:
|
||||
self.assertTrue(
|
||||
all(val is not None for key, val in service.items()
|
||||
if key in ['id', 'name', 'host', 'status',
|
||||
'last_seen_up']))
|
||||
|
||||
# Verify self links.
|
||||
for service in body['services']:
|
||||
self.validate_self_link('services', service['id'],
|
||||
service['links'][0]['href'])
|
@ -1,69 +0,0 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2016 b<>com
|
||||
#
|
||||
# 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 __future__ import unicode_literals
|
||||
|
||||
from tempest.lib import decorators
|
||||
|
||||
from watcher_tempest_plugin.tests.api.admin import base
|
||||
|
||||
|
||||
class TestShowListStrategy(base.BaseInfraOptimTest):
|
||||
"""Tests for strategies"""
|
||||
|
||||
DUMMY_STRATEGY = "dummy"
|
||||
|
||||
@classmethod
|
||||
def resource_setup(cls):
|
||||
super(TestShowListStrategy, cls).resource_setup()
|
||||
|
||||
def assert_expected(self, expected, actual,
|
||||
keys=('created_at', 'updated_at', 'deleted_at')):
|
||||
super(TestShowListStrategy, self).assert_expected(
|
||||
expected, actual, keys)
|
||||
|
||||
@decorators.attr(type='smoke')
|
||||
def test_show_strategy(self):
|
||||
_, strategy = self.client.show_strategy(self.DUMMY_STRATEGY)
|
||||
|
||||
self.assertEqual(self.DUMMY_STRATEGY, strategy['name'])
|
||||
self.assertIn("display_name", strategy.keys())
|
||||
|
||||
@decorators.attr(type='smoke')
|
||||
def test_show_strategy_with_links(self):
|
||||
_, strategy = self.client.show_strategy(self.DUMMY_STRATEGY)
|
||||
self.assertIn('links', strategy.keys())
|
||||
self.assertEqual(2, len(strategy['links']))
|
||||
self.assertIn(strategy['uuid'],
|
||||
strategy['links'][0]['href'])
|
||||
|
||||
@decorators.attr(type="smoke")
|
||||
def test_list_strategies(self):
|
||||
_, body = self.client.list_strategies()
|
||||
self.assertIn('strategies', body)
|
||||
strategies = body['strategies']
|
||||
self.assertIn(self.DUMMY_STRATEGY,
|
||||
[i['name'] for i in body['strategies']])
|
||||
|
||||
for strategy in strategies:
|
||||
self.assertTrue(
|
||||
all(val is not None for key, val in strategy.items()
|
||||
if key in ['uuid', 'name', 'display_name', 'goal_uuid']))
|
||||
|
||||
# Verify self links.
|
||||
for strategy in body['strategies']:
|
||||
self.validate_self_link('strategies', strategy['uuid'],
|
||||
strategy['links'][0]['href'])
|
@ -1,185 +0,0 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2016 b<>com
|
||||
#
|
||||
#
|
||||
# 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 __future__ import unicode_literals
|
||||
|
||||
import time
|
||||
|
||||
from oslo_log import log
|
||||
from tempest import config
|
||||
from tempest import exceptions
|
||||
from tempest.lib.common.utils import data_utils
|
||||
from tempest.lib.common.utils import test_utils
|
||||
|
||||
from watcher_tempest_plugin import infra_optim_clients as clients
|
||||
from watcher_tempest_plugin.tests.scenario import manager
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
CONF = config.CONF
|
||||
|
||||
|
||||
class BaseInfraOptimScenarioTest(manager.ScenarioTest):
|
||||
"""Base class for Infrastructure Optimization API tests."""
|
||||
|
||||
# States where the object is waiting for some event to perform a transition
|
||||
IDLE_STATES = ('RECOMMENDED', 'FAILED', 'SUCCEEDED', 'CANCELLED')
|
||||
# States where the object can only be DELETED (end of its life-cycle)
|
||||
FINISHED_STATES = ('FAILED', 'SUCCEEDED', 'CANCELLED', 'SUPERSEDED')
|
||||
|
||||
@classmethod
|
||||
def setup_credentials(cls):
|
||||
cls._check_network_config()
|
||||
super(BaseInfraOptimScenarioTest, cls).setup_credentials()
|
||||
cls.mgr = clients.AdminManager()
|
||||
|
||||
@classmethod
|
||||
def setup_clients(cls):
|
||||
super(BaseInfraOptimScenarioTest, cls).setup_clients()
|
||||
cls.client = cls.mgr.io_client
|
||||
|
||||
@classmethod
|
||||
def resource_setup(cls):
|
||||
super(BaseInfraOptimScenarioTest, cls).resource_setup()
|
||||
|
||||
@classmethod
|
||||
def resource_cleanup(cls):
|
||||
"""Ensure that all created objects get destroyed."""
|
||||
super(BaseInfraOptimScenarioTest, cls).resource_cleanup()
|
||||
|
||||
@classmethod
|
||||
def wait_for(cls, condition, timeout=30):
|
||||
start_time = time.time()
|
||||
while time.time() - start_time < timeout:
|
||||
if condition():
|
||||
break
|
||||
time.sleep(.5)
|
||||
|
||||
@classmethod
|
||||
def _check_network_config(cls):
|
||||
if not CONF.network.public_network_id:
|
||||
msg = 'public network not defined.'
|
||||
LOG.error(msg)
|
||||
raise exceptions.InvalidConfiguration(msg)
|
||||
|
||||
@classmethod
|
||||
def _are_all_action_plans_finished(cls):
|
||||
_, action_plans = cls.client.list_action_plans()
|
||||
return all([ap['state'] in cls.FINISHED_STATES
|
||||
for ap in action_plans['action_plans']])
|
||||
|
||||
def wait_for_all_action_plans_to_finish(self):
|
||||
assert test_utils.call_until_true(
|
||||
func=self._are_all_action_plans_finished,
|
||||
duration=300,
|
||||
sleep_for=5
|
||||
)
|
||||
|
||||
# ### AUDIT TEMPLATES ### #
|
||||
|
||||
def create_audit_template(self, goal, name=None, description=None,
|
||||
strategy=None):
|
||||
"""Wrapper utility for creating a test audit template
|
||||
|
||||
:param goal: Goal UUID or name related to the audit template.
|
||||
:param name: The name of the audit template. Default: My Audit Template
|
||||
:param description: The description of the audit template.
|
||||
:param strategy: Strategy UUID or name related to the audit template.
|
||||
:return: A tuple with The HTTP response and its body
|
||||
"""
|
||||
description = description or data_utils.rand_name(
|
||||
'test-audit_template')
|
||||
resp, body = self.client.create_audit_template(
|
||||
name=name, description=description, goal=goal, strategy=strategy)
|
||||
|
||||
self.addCleanup(
|
||||
self.delete_audit_template,
|
||||
audit_template_uuid=body["uuid"]
|
||||
)
|
||||
|
||||
return resp, body
|
||||
|
||||
def delete_audit_template(self, audit_template_uuid):
|
||||
"""Deletes a audit_template having the specified UUID
|
||||
|
||||
:param audit_template_uuid: The unique identifier of the audit template
|
||||
:return: Server response
|
||||
"""
|
||||
resp, _ = self.client.delete_audit_template(audit_template_uuid)
|
||||
return resp
|
||||
|
||||
# ### AUDITS ### #
|
||||
|
||||
def create_audit(self, audit_template_uuid, audit_type='ONESHOT',
|
||||
state=None, interval=None, parameters=None):
|
||||
"""Wrapper utility for creating a test audit
|
||||
|
||||
:param audit_template_uuid: Audit Template UUID this audit will use
|
||||
:param type: Audit type (either ONESHOT or CONTINUOUS)
|
||||
:param state: Audit state (str)
|
||||
:param interval: Audit interval in seconds (int)
|
||||
:param parameters: list of execution parameters
|
||||
:return: A tuple with The HTTP response and its body
|
||||
"""
|
||||
resp, body = self.client.create_audit(
|
||||
audit_template_uuid=audit_template_uuid, audit_type=audit_type,
|
||||
state=state, interval=interval, parameters=parameters)
|
||||
|
||||
self.addCleanup(self.delete_audit, audit_uuid=body["uuid"])
|
||||
return resp, body
|
||||
|
||||
def delete_audit(self, audit_uuid):
|
||||
"""Deletes an audit having the specified UUID
|
||||
|
||||
:param audit_uuid: The unique identifier of the audit.
|
||||
:return: the HTTP response
|
||||
"""
|
||||
|
||||
_, action_plans = self.client.list_action_plans(audit_uuid=audit_uuid)
|
||||
for action_plan in action_plans.get("action_plans", []):
|
||||
self.delete_action_plan(action_plan_uuid=action_plan["uuid"])
|
||||
|
||||
resp, _ = self.client.delete_audit(audit_uuid)
|
||||
return resp
|
||||
|
||||
def has_audit_succeeded(self, audit_uuid):
|
||||
_, audit = self.client.show_audit(audit_uuid)
|
||||
if audit.get('state') in ('FAILED', 'CANCELLED'):
|
||||
raise ValueError()
|
||||
|
||||
return audit.get('state') == 'SUCCEEDED'
|
||||
|
||||
@classmethod
|
||||
def has_audit_finished(cls, audit_uuid):
|
||||
_, audit = cls.client.show_audit(audit_uuid)
|
||||
return audit.get('state') in cls.FINISHED_STATES
|
||||
|
||||
# ### ACTION PLANS ### #
|
||||
|
||||
def delete_action_plan(self, action_plan_uuid):
|
||||
"""Deletes an action plan having the specified UUID
|
||||
|
||||
:param action_plan_uuid: The unique identifier of the action plan.
|
||||
:return: the HTTP response
|
||||
"""
|
||||
resp, _ = self.client.delete_action_plan(action_plan_uuid)
|
||||
return resp
|
||||
|
||||
def has_action_plan_finished(self, action_plan_uuid):
|
||||
_, action_plan = self.client.show_action_plan(action_plan_uuid)
|
||||
return action_plan.get('state') in ('FAILED', 'SUCCEEDED', 'CANCELLED',
|
||||
'SUPERSEDED')
|
@ -1,206 +0,0 @@
|
||||
# Copyright 2012 OpenStack Foundation
|
||||
# Copyright 2013 IBM Corp.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from oslo_log import log
|
||||
|
||||
from tempest.common import compute
|
||||
from tempest.common import waiters
|
||||
from tempest import config
|
||||
from tempest.lib.common.utils import data_utils
|
||||
from tempest.lib.common.utils import test_utils
|
||||
from tempest.lib import exceptions as lib_exc
|
||||
import tempest.test
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class ScenarioTest(tempest.test.BaseTestCase):
|
||||
"""Base class for scenario tests. Uses tempest own clients. """
|
||||
|
||||
credentials = ['primary']
|
||||
|
||||
@classmethod
|
||||
def setup_clients(cls):
|
||||
super(ScenarioTest, cls).setup_clients()
|
||||
# Clients (in alphabetical order)
|
||||
cls.flavors_client = cls.os_primary.flavors_client
|
||||
cls.compute_floating_ips_client = (
|
||||
cls.os_primary.compute_floating_ips_client)
|
||||
if CONF.service_available.glance:
|
||||
# Check if glance v1 is available to determine which client to use.
|
||||
if CONF.image_feature_enabled.api_v1:
|
||||
cls.image_client = cls.os_primary.image_client
|
||||
elif CONF.image_feature_enabled.api_v2:
|
||||
cls.image_client = cls.os_primary.image_client_v2
|
||||
else:
|
||||
raise lib_exc.InvalidConfiguration(
|
||||
'Either api_v1 or api_v2 must be True in '
|
||||
'[image-feature-enabled].')
|
||||
# Compute image client
|
||||
cls.compute_images_client = cls.os_primary.compute_images_client
|
||||
cls.keypairs_client = cls.os_primary.keypairs_client
|
||||
# Nova security groups client
|
||||
cls.compute_security_groups_client = (
|
||||
cls.os_primary.compute_security_groups_client)
|
||||
cls.compute_security_group_rules_client = (
|
||||
cls.os_primary.compute_security_group_rules_client)
|
||||
cls.servers_client = cls.os_primary.servers_client
|
||||
cls.interface_client = cls.os_primary.interfaces_client
|
||||
# Neutron network client
|
||||
cls.networks_client = cls.os_primary.networks_client
|
||||
cls.ports_client = cls.os_primary.ports_client
|
||||
cls.routers_client = cls.os_primary.routers_client
|
||||
cls.subnets_client = cls.os_primary.subnets_client
|
||||
cls.floating_ips_client = cls.os_primary.floating_ips_client
|
||||
cls.security_groups_client = cls.os_primary.security_groups_client
|
||||
cls.security_group_rules_client = (
|
||||
cls.os_primary.security_group_rules_client)
|
||||
|
||||
if CONF.volume_feature_enabled.api_v2:
|
||||
cls.volumes_client = cls.os_primary.volumes_v2_client
|
||||
cls.snapshots_client = cls.os_primary.snapshots_v2_client
|
||||
else:
|
||||
cls.volumes_client = cls.os_primary.volumes_client
|
||||
cls.snapshots_client = cls.os_primary.snapshots_client
|
||||
|
||||
# ## Test functions library
|
||||
#
|
||||
# The create_[resource] functions only return body and discard the
|
||||
# resp part which is not used in scenario tests
|
||||
|
||||
def _create_port(self, network_id, client=None, namestart='port-quotatest',
|
||||
**kwargs):
|
||||
if not client:
|
||||
client = self.ports_client
|
||||
name = data_utils.rand_name(namestart)
|
||||
result = client.create_port(
|
||||
name=name,
|
||||
network_id=network_id,
|
||||
**kwargs)
|
||||
self.assertIsNotNone(result, 'Unable to allocate port')
|
||||
port = result['port']
|
||||
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
|
||||
client.delete_port, port['id'])
|
||||
return port
|
||||
|
||||
def create_keypair(self, client=None):
|
||||
if not client:
|
||||
client = self.keypairs_client
|
||||
name = data_utils.rand_name(self.__class__.__name__)
|
||||
# We don't need to create a keypair by pubkey in scenario
|
||||
body = client.create_keypair(name=name)
|
||||
self.addCleanup(client.delete_keypair, name)
|
||||
return body['keypair']
|
||||
|
||||
def create_server(self, name=None, image_id=None, flavor=None,
|
||||
validatable=False, wait_until='ACTIVE',
|
||||
clients=None, **kwargs):
|
||||
"""Wrapper utility that returns a test server.
|
||||
|
||||
This wrapper utility calls the common create test server and
|
||||
returns a test server. The purpose of this wrapper is to minimize
|
||||
the impact on the code of the tests already using this
|
||||
function.
|
||||
"""
|
||||
|
||||
# NOTE(jlanoux): As a first step, ssh checks in the scenario
|
||||
# tests need to be run regardless of the run_validation and
|
||||
# validatable parameters and thus until the ssh validation job
|
||||
# becomes voting in CI. The test resources management and IP
|
||||
# association are taken care of in the scenario tests.
|
||||
# Therefore, the validatable parameter is set to false in all
|
||||
# those tests. In this way create_server just return a standard
|
||||
# server and the scenario tests always perform ssh checks.
|
||||
|
||||
# Needed for the cross_tenant_traffic test:
|
||||
if clients is None:
|
||||
clients = self.os_primary
|
||||
|
||||
if name is None:
|
||||
name = data_utils.rand_name(self.__class__.__name__ + "-server")
|
||||
|
||||
vnic_type = CONF.network.port_vnic_type
|
||||
|
||||
# If vnic_type is configured create port for
|
||||
# every network
|
||||
if vnic_type:
|
||||
ports = []
|
||||
|
||||
create_port_body = {'binding:vnic_type': vnic_type,
|
||||
'namestart': 'port-smoke'}
|
||||
if kwargs:
|
||||
# Convert security group names to security group ids
|
||||
# to pass to create_port
|
||||
if 'security_groups' in kwargs:
|
||||
security_groups = \
|
||||
clients.security_groups_client.list_security_groups(
|
||||
).get('security_groups')
|
||||
sec_dict = dict([(s['name'], s['id'])
|
||||
for s in security_groups])
|
||||
|
||||
sec_groups_names = [s['name'] for s in kwargs.pop(
|
||||
'security_groups')]
|
||||
security_groups_ids = [sec_dict[s]
|
||||
for s in sec_groups_names]
|
||||
|
||||
if security_groups_ids:
|
||||
create_port_body[
|
||||
'security_groups'] = security_groups_ids
|
||||
networks = kwargs.pop('networks', [])
|
||||
else:
|
||||
networks = []
|
||||
|
||||
# If there are no networks passed to us we look up
|
||||
# for the project's private networks and create a port.
|
||||
# The same behaviour as we would expect when passing
|
||||
# the call to the clients with no networks
|
||||
if not networks:
|
||||
networks = clients.networks_client.list_networks(
|
||||
**{'router:external': False, 'fields': 'id'})['networks']
|
||||
|
||||
# It's net['uuid'] if networks come from kwargs
|
||||
# and net['id'] if they come from
|
||||
# clients.networks_client.list_networks
|
||||
for net in networks:
|
||||
net_id = net.get('uuid', net.get('id'))
|
||||
if 'port' not in net:
|
||||
port = self._create_port(network_id=net_id,
|
||||
client=clients.ports_client,
|
||||
**create_port_body)
|
||||
ports.append({'port': port['id']})
|
||||
else:
|
||||
ports.append({'port': net['port']})
|
||||
if ports:
|
||||
kwargs['networks'] = ports
|
||||
self.ports = ports
|
||||
|
||||
tenant_network = self.get_tenant_network()
|
||||
|
||||
body, servers = compute.create_test_server(
|
||||
clients,
|
||||
tenant_network=tenant_network,
|
||||
wait_until=wait_until,
|
||||
name=name, flavor=flavor,
|
||||
image_id=image_id, **kwargs)
|
||||
|
||||
self.addCleanup(waiters.wait_for_server_termination,
|
||||
clients.servers_client, body['id'])
|
||||
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
|
||||
clients.servers_client.delete_server, body['id'])
|
||||
server = clients.servers_client.show_server(body['id'])['server']
|
||||
return server
|
@ -1,340 +0,0 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2016 b<>com
|
||||
#
|
||||
#
|
||||
# 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 __future__ import unicode_literals
|
||||
|
||||
import collections
|
||||
import functools
|
||||
|
||||
from tempest import config
|
||||
from tempest.lib.common.utils import test_utils
|
||||
|
||||
from watcher_tempest_plugin.tests.scenario import base
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
|
||||
class TestExecuteActionsViaActuator(base.BaseInfraOptimScenarioTest):
|
||||
|
||||
scenarios = [
|
||||
("nop", {"actions": [
|
||||
{"action_type": "nop",
|
||||
"input_parameters": {
|
||||
"message": "hello World"}}]}),
|
||||
("sleep", {"actions": [
|
||||
{"action_type": "sleep",
|
||||
"input_parameters": {
|
||||
"duration": 1.0}}]}),
|
||||
("change_nova_service_state", {"actions": [
|
||||
{"action_type": "change_nova_service_state",
|
||||
"input_parameters": {
|
||||
"state": "enabled"},
|
||||
"filling_function":
|
||||
"_prerequisite_param_for_"
|
||||
"change_nova_service_state_action"}]}),
|
||||
("resize", {"actions": [
|
||||
{"action_type": "resize",
|
||||
"filling_function": "_prerequisite_param_for_resize_action"}]}),
|
||||
("migrate", {"actions": [
|
||||
{"action_type": "migrate",
|
||||
"input_parameters": {
|
||||
"migration_type": "live"},
|
||||
"filling_function": "_prerequisite_param_for_migrate_action"},
|
||||
{"action_type": "migrate",
|
||||
"filling_function": "_prerequisite_param_for_migrate_action"}]})
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def resource_setup(cls):
|
||||
super(TestExecuteActionsViaActuator, cls).resource_setup()
|
||||
if CONF.compute.min_compute_nodes < 2:
|
||||
raise cls.skipException(
|
||||
"Less than 2 compute nodes, skipping multinode tests.")
|
||||
if not CONF.compute_feature_enabled.live_migration:
|
||||
raise cls.skipException("Live migration is not enabled")
|
||||
|
||||
cls.initial_compute_nodes_setup = cls.get_compute_nodes_setup()
|
||||
enabled_compute_nodes = [cn for cn in cls.initial_compute_nodes_setup
|
||||
if cn.get('status') == 'enabled']
|
||||
|
||||
cls.wait_for_compute_node_setup()
|
||||
|
||||
if len(enabled_compute_nodes) < 2:
|
||||
raise cls.skipException(
|
||||
"Less than 2 compute nodes are enabled, "
|
||||
"skipping multinode tests.")
|
||||
|
||||
@classmethod
|
||||
def get_compute_nodes_setup(cls):
|
||||
services_client = cls.mgr.services_client
|
||||
available_services = services_client.list_services()['services']
|
||||
|
||||
return [srv for srv in available_services
|
||||
if srv.get('binary') == 'nova-compute']
|
||||
|
||||
@classmethod
|
||||
def wait_for_compute_node_setup(cls):
|
||||
|
||||
def _are_compute_nodes_setup():
|
||||
try:
|
||||
hypervisors_client = cls.mgr.hypervisor_client
|
||||
hypervisors = hypervisors_client.list_hypervisors(
|
||||
detail=True)['hypervisors']
|
||||
available_hypervisors = set(
|
||||
hyp['hypervisor_hostname'] for hyp in hypervisors)
|
||||
available_services = set(
|
||||
service['host']
|
||||
for service in cls.get_compute_nodes_setup())
|
||||
|
||||
return (
|
||||
available_hypervisors == available_services and
|
||||
len(hypervisors) >= 2)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
assert test_utils.call_until_true(
|
||||
func=_are_compute_nodes_setup,
|
||||
duration=600,
|
||||
sleep_for=2
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def rollback_compute_nodes_status(cls):
|
||||
current_compute_nodes_setup = cls.get_compute_nodes_setup()
|
||||
for cn_setup in current_compute_nodes_setup:
|
||||
cn_hostname = cn_setup.get('host')
|
||||
matching_cns = [
|
||||
cns for cns in cls.initial_compute_nodes_setup
|
||||
if cns.get('host') == cn_hostname
|
||||
]
|
||||
initial_cn_setup = matching_cns[0] # Should return a single result
|
||||
if cn_setup.get('status') != initial_cn_setup.get('status'):
|
||||
if initial_cn_setup.get('status') == 'enabled':
|
||||
rollback_func = cls.mgr.services_client.enable_service
|
||||
else:
|
||||
rollback_func = cls.mgr.services_client.disable_service
|
||||
rollback_func(binary='nova-compute', host=cn_hostname)
|
||||
|
||||
def _create_one_instance_per_host(self):
|
||||
"""Create 1 instance per compute node
|
||||
|
||||
This goes up to the min_compute_nodes threshold so that things don't
|
||||
get crazy if you have 1000 compute nodes but set min to 3.
|
||||
"""
|
||||
host_client = self.mgr.hosts_client
|
||||
all_hosts = host_client.list_hosts()['hosts']
|
||||
compute_nodes = [x for x in all_hosts if x['service'] == 'compute']
|
||||
|
||||
created_servers = []
|
||||
for _ in compute_nodes[:CONF.compute.min_compute_nodes]:
|
||||
# by getting to active state here, this means this has
|
||||
# landed on the host in question.
|
||||
created_servers.append(
|
||||
self.create_server(image_id=CONF.compute.image_ref,
|
||||
wait_until='ACTIVE',
|
||||
clients=self.mgr))
|
||||
|
||||
return created_servers
|
||||
|
||||
def _get_flavors(self):
|
||||
return self.mgr.flavors_client.list_flavors()['flavors']
|
||||
|
||||
def _prerequisite_param_for_migrate_action(self):
|
||||
created_instances = self._create_one_instance_per_host()
|
||||
instance = created_instances[0]
|
||||
source_node = created_instances[0]["OS-EXT-SRV-ATTR:host"]
|
||||
destination_node = created_instances[-1]["OS-EXT-SRV-ATTR:host"]
|
||||
|
||||
parameters = {
|
||||
"resource_id": instance['id'],
|
||||
"migration_type": "live",
|
||||
"source_node": source_node,
|
||||
"destination_node": destination_node
|
||||
}
|
||||
|
||||
return parameters
|
||||
|
||||
def _prerequisite_param_for_resize_action(self):
|
||||
created_instances = self._create_one_instance_per_host()
|
||||
instance = created_instances[0]
|
||||
current_flavor_id = instance['flavor']['id']
|
||||
|
||||
flavors = self._get_flavors()
|
||||
new_flavors = [f for f in flavors if f['id'] != current_flavor_id]
|
||||
new_flavor = new_flavors[0]
|
||||
|
||||
parameters = {
|
||||
"resource_id": instance['id'],
|
||||
"flavor": new_flavor['name']
|
||||
}
|
||||
|
||||
return parameters
|
||||
|
||||
def _prerequisite_param_for_change_nova_service_state_action(self):
|
||||
enabled_compute_nodes = [cn for cn in
|
||||
self.initial_compute_nodes_setup
|
||||
if cn.get('status') == 'enabled']
|
||||
enabled_compute_node = enabled_compute_nodes[0]
|
||||
|
||||
parameters = {
|
||||
"resource_id": enabled_compute_node['host'],
|
||||
"state": "enabled"
|
||||
}
|
||||
|
||||
return parameters
|
||||
|
||||
def _fill_actions(self, actions):
|
||||
for action in actions:
|
||||
filling_function_name = action.pop('filling_function', None)
|
||||
|
||||
if filling_function_name is not None:
|
||||
filling_function = getattr(self, filling_function_name, None)
|
||||
|
||||
if filling_function is not None:
|
||||
parameters = filling_function()
|
||||
|
||||
resource_id = parameters.pop('resource_id', None)
|
||||
|
||||
if resource_id is not None:
|
||||
action['resource_id'] = resource_id
|
||||
|
||||
input_parameters = action.get('input_parameters', None)
|
||||
|
||||
if input_parameters is not None:
|
||||
parameters.update(input_parameters)
|
||||
input_parameters.update(parameters)
|
||||
else:
|
||||
action['input_parameters'] = parameters
|
||||
|
||||
def _execute_actions(self, actions):
|
||||
self.wait_for_all_action_plans_to_finish()
|
||||
|
||||
_, goal = self.client.show_goal("unclassified")
|
||||
_, strategy = self.client.show_strategy("actuator")
|
||||
_, audit_template = self.create_audit_template(
|
||||
goal['uuid'], strategy=strategy['uuid'])
|
||||
_, audit = self.create_audit(
|
||||
audit_template['uuid'], parameters={"actions": actions})
|
||||
|
||||
self.assertTrue(test_utils.call_until_true(
|
||||
func=functools.partial(self.has_audit_succeeded, audit['uuid']),
|
||||
duration=30,
|
||||
sleep_for=.5
|
||||
))
|
||||
_, action_plans = self.client.list_action_plans(
|
||||
audit_uuid=audit['uuid'])
|
||||
action_plan = action_plans['action_plans'][0]
|
||||
|
||||
_, action_plan = self.client.show_action_plan(action_plan['uuid'])
|
||||
|
||||
# Execute the action plan
|
||||
_, updated_ap = self.client.start_action_plan(action_plan['uuid'])
|
||||
|
||||
self.assertTrue(test_utils.call_until_true(
|
||||
func=functools.partial(
|
||||
self.has_action_plan_finished, action_plan['uuid']),
|
||||
duration=300,
|
||||
sleep_for=1
|
||||
))
|
||||
_, finished_ap = self.client.show_action_plan(action_plan['uuid'])
|
||||
_, action_list = self.client.list_actions(
|
||||
action_plan_uuid=finished_ap["uuid"])
|
||||
|
||||
self.assertIn(updated_ap['state'], ('PENDING', 'ONGOING'))
|
||||
self.assertIn(finished_ap['state'], ('SUCCEEDED', 'SUPERSEDED'))
|
||||
|
||||
expected_action_counter = collections.Counter(
|
||||
act['action_type'] for act in actions)
|
||||
action_counter = collections.Counter(
|
||||
act['action_type'] for act in action_list['actions'])
|
||||
|
||||
self.assertEqual(expected_action_counter, action_counter)
|
||||
|
||||
def test_execute_nop(self):
|
||||
self.addCleanup(self.rollback_compute_nodes_status)
|
||||
|
||||
actions = [{
|
||||
"action_type": "nop",
|
||||
"input_parameters": {"message": "hello World"}}]
|
||||
self._execute_actions(actions)
|
||||
|
||||
def test_execute_sleep(self):
|
||||
self.addCleanup(self.rollback_compute_nodes_status)
|
||||
|
||||
actions = [
|
||||
{"action_type": "sleep",
|
||||
"input_parameters": {"duration": 1.0}}
|
||||
]
|
||||
self._execute_actions(actions)
|
||||
|
||||
def test_execute_change_nova_service_state(self):
|
||||
self.addCleanup(self.rollback_compute_nodes_status)
|
||||
|
||||
enabled_compute_nodes = [
|
||||
cn for cn in self.initial_compute_nodes_setup
|
||||
if cn.get('status') == 'enabled']
|
||||
|
||||
enabled_compute_node = enabled_compute_nodes[0]
|
||||
actions = [
|
||||
{"action_type": "change_nova_service_state",
|
||||
"resource_id": enabled_compute_node['host'],
|
||||
"input_parameters": {"state": "enabled"}}
|
||||
]
|
||||
self._execute_actions(actions)
|
||||
|
||||
def test_execute_resize(self):
|
||||
self.addCleanup(self.rollback_compute_nodes_status)
|
||||
|
||||
created_instances = self._create_one_instance_per_host()
|
||||
instance = created_instances[0]
|
||||
current_flavor_id = instance['flavor']['id']
|
||||
|
||||
flavors = self._get_flavors()
|
||||
new_flavors = [f for f in flavors if f['id'] != current_flavor_id]
|
||||
new_flavor = new_flavors[0]
|
||||
|
||||
actions = [
|
||||
{"action_type": "resize",
|
||||
"resource_id": instance['id'],
|
||||
"input_parameters": {"flavor": new_flavor['name']}}
|
||||
]
|
||||
self._execute_actions(actions)
|
||||
|
||||
def test_execute_migrate(self):
|
||||
self.addCleanup(self.rollback_compute_nodes_status)
|
||||
|
||||
created_instances = self._create_one_instance_per_host()
|
||||
instance = created_instances[0]
|
||||
source_node = created_instances[0]["OS-EXT-SRV-ATTR:host"]
|
||||
destination_node = created_instances[-1]["OS-EXT-SRV-ATTR:host"]
|
||||
actions = [
|
||||
{"action_type": "migrate",
|
||||
"resource_id": instance['id'],
|
||||
"input_parameters": {
|
||||
"migration_type": "live",
|
||||
"source_node": source_node,
|
||||
"destination_node": destination_node}}
|
||||
]
|
||||
self._execute_actions(actions)
|
||||
|
||||
def test_execute_scenarios(self):
|
||||
self.addCleanup(self.rollback_compute_nodes_status)
|
||||
|
||||
for _, scenario in self.scenarios:
|
||||
actions = scenario['actions']
|
||||
self._fill_actions(actions)
|
||||
self._execute_actions(actions)
|
@ -1,191 +0,0 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2016 b<>com
|
||||
#
|
||||
#
|
||||
# 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 __future__ import unicode_literals
|
||||
|
||||
import functools
|
||||
|
||||
from tempest import config
|
||||
from tempest.lib.common.utils import test_utils
|
||||
|
||||
from watcher_tempest_plugin.tests.scenario import base
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
|
||||
class TestExecuteBasicStrategy(base.BaseInfraOptimScenarioTest):
|
||||
"""Tests for action plans"""
|
||||
|
||||
GOAL_NAME = "server_consolidation"
|
||||
|
||||
@classmethod
|
||||
def skip_checks(cls):
|
||||
super(TestExecuteBasicStrategy, cls).skip_checks()
|
||||
|
||||
@classmethod
|
||||
def resource_setup(cls):
|
||||
super(TestExecuteBasicStrategy, cls).resource_setup()
|
||||
if CONF.compute.min_compute_nodes < 2:
|
||||
raise cls.skipException(
|
||||
"Less than 2 compute nodes, skipping multinode tests.")
|
||||
if not CONF.compute_feature_enabled.live_migration:
|
||||
raise cls.skipException("Live migration is not enabled")
|
||||
|
||||
cls.initial_compute_nodes_setup = cls.get_compute_nodes_setup()
|
||||
enabled_compute_nodes = [cn for cn in cls.initial_compute_nodes_setup
|
||||
if cn.get('status') == 'enabled']
|
||||
|
||||
cls.wait_for_compute_node_setup()
|
||||
|
||||
if len(enabled_compute_nodes) < 2:
|
||||
raise cls.skipException(
|
||||
"Less than 2 compute nodes are enabled, "
|
||||
"skipping multinode tests.")
|
||||
|
||||
@classmethod
|
||||
def get_compute_nodes_setup(cls):
|
||||
services_client = cls.mgr.services_client
|
||||
available_services = services_client.list_services()['services']
|
||||
|
||||
return [srv for srv in available_services
|
||||
if srv.get('binary') == 'nova-compute']
|
||||
|
||||
@classmethod
|
||||
def wait_for_compute_node_setup(cls):
|
||||
|
||||
def _are_compute_nodes_setup():
|
||||
try:
|
||||
hypervisors_client = cls.mgr.hypervisor_client
|
||||
hypervisors = hypervisors_client.list_hypervisors(
|
||||
detail=True)['hypervisors']
|
||||
available_hypervisors = set(
|
||||
hyp['hypervisor_hostname'] for hyp in hypervisors)
|
||||
available_services = set(
|
||||
service['host']
|
||||
for service in cls.get_compute_nodes_setup())
|
||||
|
||||
return (
|
||||
available_hypervisors == available_services and
|
||||
len(hypervisors) >= 2)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
assert test_utils.call_until_true(
|
||||
func=_are_compute_nodes_setup,
|
||||
duration=600,
|
||||
sleep_for=2
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def rollback_compute_nodes_status(cls):
|
||||
current_compute_nodes_setup = cls.get_compute_nodes_setup()
|
||||
for cn_setup in current_compute_nodes_setup:
|
||||
cn_hostname = cn_setup.get('host')
|
||||
matching_cns = [
|
||||
cns for cns in cls.initial_compute_nodes_setup
|
||||
if cns.get('host') == cn_hostname
|
||||
]
|
||||
initial_cn_setup = matching_cns[0] # Should return a single result
|
||||
if cn_setup.get('status') != initial_cn_setup.get('status'):
|
||||
if initial_cn_setup.get('status') == 'enabled':
|
||||
rollback_func = cls.mgr.services_client.enable_service
|
||||
else:
|
||||
rollback_func = cls.mgr.services_client.disable_service
|
||||
rollback_func(binary='nova-compute', host=cn_hostname)
|
||||
|
||||
def _create_one_instance_per_host(self):
|
||||
"""Create 1 instance per compute node
|
||||
|
||||
This goes up to the min_compute_nodes threshold so that things don't
|
||||
get crazy if you have 1000 compute nodes but set min to 3.
|
||||
"""
|
||||
host_client = self.mgr.hosts_client
|
||||
all_hosts = host_client.list_hosts()['hosts']
|
||||
compute_nodes = [x for x in all_hosts if x['service'] == 'compute']
|
||||
|
||||
for idx, _ in enumerate(
|
||||
compute_nodes[:CONF.compute.min_compute_nodes], start=1):
|
||||
# by getting to active state here, this means this has
|
||||
# landed on the host in question.
|
||||
self.create_server(
|
||||
name="instance-%d" % idx,
|
||||
image_id=CONF.compute.image_ref,
|
||||
wait_until='ACTIVE',
|
||||
clients=self.mgr)
|
||||
|
||||
def test_execute_basic_action_plan(self):
|
||||
"""Execute an action plan based on the BASIC strategy
|
||||
|
||||
- create an audit template with the basic strategy
|
||||
- run the audit to create an action plan
|
||||
- get the action plan
|
||||
- run the action plan
|
||||
- get results and make sure it succeeded
|
||||
"""
|
||||
self.addCleanup(self.rollback_compute_nodes_status)
|
||||
self._create_one_instance_per_host()
|
||||
|
||||
_, goal = self.client.show_goal(self.GOAL_NAME)
|
||||
_, strategy = self.client.show_strategy("basic")
|
||||
_, audit_template = self.create_audit_template(
|
||||
goal['uuid'], strategy=strategy['uuid'])
|
||||
_, audit = self.create_audit(audit_template['uuid'])
|
||||
|
||||
try:
|
||||
self.assertTrue(test_utils.call_until_true(
|
||||
func=functools.partial(
|
||||
self.has_audit_finished, audit['uuid']),
|
||||
duration=600,
|
||||
sleep_for=2
|
||||
))
|
||||
except ValueError:
|
||||
self.fail("The audit has failed!")
|
||||
|
||||
_, finished_audit = self.client.show_audit(audit['uuid'])
|
||||
if finished_audit.get('state') in ('FAILED', 'CANCELLED', 'SUSPENDED'):
|
||||
self.fail("The audit ended in unexpected state: %s!"
|
||||
% finished_audit.get('state'))
|
||||
|
||||
_, action_plans = self.client.list_action_plans(
|
||||
audit_uuid=audit['uuid'])
|
||||
action_plan = action_plans['action_plans'][0]
|
||||
|
||||
_, action_plan = self.client.show_action_plan(action_plan['uuid'])
|
||||
|
||||
if action_plan['state'] in ('SUPERSEDED', 'SUCCEEDED'):
|
||||
# This means the action plan is superseded so we cannot trigger it,
|
||||
# or it is empty.
|
||||
return
|
||||
|
||||
# Execute the action by changing its state to PENDING
|
||||
_, updated_ap = self.client.start_action_plan(action_plan['uuid'])
|
||||
|
||||
self.assertTrue(test_utils.call_until_true(
|
||||
func=functools.partial(
|
||||
self.has_action_plan_finished, action_plan['uuid']),
|
||||
duration=600,
|
||||
sleep_for=2
|
||||
))
|
||||
_, finished_ap = self.client.show_action_plan(action_plan['uuid'])
|
||||
_, action_list = self.client.list_actions(
|
||||
action_plan_uuid=finished_ap["uuid"])
|
||||
|
||||
self.assertIn(updated_ap['state'], ('PENDING', 'ONGOING'))
|
||||
self.assertIn(finished_ap['state'], ('SUCCEEDED', 'SUPERSEDED'))
|
||||
|
||||
for action in action_list['actions']:
|
||||
self.assertEqual('SUCCEEDED', action.get('state'))
|
@ -1,85 +0,0 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2016 b<>com
|
||||
#
|
||||
#
|
||||
# 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 __future__ import unicode_literals
|
||||
|
||||
import collections
|
||||
import functools
|
||||
|
||||
from tempest.lib.common.utils import test_utils
|
||||
|
||||
from watcher_tempest_plugin.tests.scenario import base
|
||||
|
||||
|
||||
class TestExecuteDummyStrategy(base.BaseInfraOptimScenarioTest):
|
||||
"""Tests for action plans"""
|
||||
|
||||
def test_execute_dummy_action_plan(self):
|
||||
"""Execute an action plan based on the 'dummy' strategy
|
||||
|
||||
- create an audit template with the 'dummy' strategy
|
||||
- run the audit to create an action plan
|
||||
- get the action plan
|
||||
- run the action plan
|
||||
- get results and make sure it succeeded
|
||||
"""
|
||||
_, goal = self.client.show_goal("dummy")
|
||||
_, audit_template = self.create_audit_template(goal['uuid'])
|
||||
_, audit = self.create_audit(audit_template['uuid'])
|
||||
|
||||
self.assertTrue(test_utils.call_until_true(
|
||||
func=functools.partial(self.has_audit_finished, audit['uuid']),
|
||||
duration=30,
|
||||
sleep_for=.5
|
||||
))
|
||||
|
||||
self.assertTrue(self.has_audit_succeeded(audit['uuid']))
|
||||
|
||||
_, action_plans = self.client.list_action_plans(
|
||||
audit_uuid=audit['uuid'])
|
||||
action_plan = action_plans['action_plans'][0]
|
||||
|
||||
_, action_plan = self.client.show_action_plan(action_plan['uuid'])
|
||||
|
||||
if action_plan['state'] in ['SUPERSEDED', 'SUCCEEDED']:
|
||||
# This means the action plan is superseded so we cannot trigger it,
|
||||
# or it is empty.
|
||||
return
|
||||
|
||||
# Execute the action by changing its state to PENDING
|
||||
_, updated_ap = self.client.start_action_plan(action_plan['uuid'])
|
||||
|
||||
self.assertTrue(test_utils.call_until_true(
|
||||
func=functools.partial(
|
||||
self.has_action_plan_finished, action_plan['uuid']),
|
||||
duration=30,
|
||||
sleep_for=.5
|
||||
))
|
||||
_, finished_ap = self.client.show_action_plan(action_plan['uuid'])
|
||||
_, action_list = self.client.list_actions(
|
||||
action_plan_uuid=finished_ap["uuid"])
|
||||
|
||||
action_counter = collections.Counter(
|
||||
act['action_type'] for act in action_list['actions'])
|
||||
|
||||
self.assertIn(updated_ap['state'], ('PENDING', 'ONGOING'))
|
||||
self.assertIn(finished_ap['state'], ('SUCCEEDED', 'SUPERSEDED'))
|
||||
|
||||
# A dummy strategy generates 2 "nop" actions and 1 "sleep" action
|
||||
self.assertEqual(3, len(action_list['actions']))
|
||||
self.assertEqual(2, action_counter.get("nop"))
|
||||
self.assertEqual(1, action_counter.get("sleep"))
|
@ -1,198 +0,0 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2016 b<>com
|
||||
#
|
||||
#
|
||||
# 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 __future__ import unicode_literals
|
||||
|
||||
import functools
|
||||
|
||||
from oslo_log import log
|
||||
from tempest import config
|
||||
from tempest.lib.common.utils import test_utils
|
||||
|
||||
from watcher_tempest_plugin.tests.scenario import base
|
||||
|
||||
CONF = config.CONF
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class TestExecuteWorkloadBalancingStrategy(base.BaseInfraOptimScenarioTest):
|
||||
"""Tests for action plans"""
|
||||
|
||||
GOAL = "workload_balancing"
|
||||
|
||||
@classmethod
|
||||
def skip_checks(cls):
|
||||
super(TestExecuteWorkloadBalancingStrategy, cls).skip_checks()
|
||||
|
||||
@classmethod
|
||||
def resource_setup(cls):
|
||||
super(TestExecuteWorkloadBalancingStrategy, cls).resource_setup()
|
||||
if CONF.compute.min_compute_nodes < 2:
|
||||
raise cls.skipException(
|
||||
"Less than 2 compute nodes, skipping multinode tests.")
|
||||
if not CONF.compute_feature_enabled.live_migration:
|
||||
raise cls.skipException("Live migration is not enabled")
|
||||
|
||||
cls.initial_compute_nodes_setup = cls.get_compute_nodes_setup()
|
||||
enabled_compute_nodes = [cn for cn in cls.initial_compute_nodes_setup
|
||||
if cn.get('status') == 'enabled']
|
||||
|
||||
cls.wait_for_compute_node_setup()
|
||||
|
||||
if len(enabled_compute_nodes) < 2:
|
||||
raise cls.skipException(
|
||||
"Less than 2 compute nodes are enabled, "
|
||||
"skipping multinode tests.")
|
||||
|
||||
@classmethod
|
||||
def get_hypervisors_setup(cls):
|
||||
hypervisors_client = cls.mgr.hypervisor_client
|
||||
hypervisors = hypervisors_client.list_hypervisors(
|
||||
detail=True)['hypervisors']
|
||||
return hypervisors
|
||||
|
||||
@classmethod
|
||||
def get_compute_nodes_setup(cls):
|
||||
services_client = cls.mgr.services_client
|
||||
available_services = services_client.list_services()['services']
|
||||
|
||||
return [srv for srv in available_services
|
||||
if srv.get('binary') == 'nova-compute']
|
||||
|
||||
def _migrate_server_to(self, server_id, dest_host, volume_backed=False):
|
||||
kwargs = dict()
|
||||
kwargs['disk_over_commit'] = False
|
||||
block_migration = (CONF.compute_feature_enabled.
|
||||
block_migration_for_live_migration and
|
||||
not volume_backed)
|
||||
body = self.mgr.servers_client.live_migrate_server(
|
||||
server_id, host=dest_host, block_migration=block_migration,
|
||||
**kwargs)
|
||||
return body
|
||||
|
||||
@classmethod
|
||||
def wait_for_compute_node_setup(cls):
|
||||
|
||||
def _are_compute_nodes_setup():
|
||||
try:
|
||||
hypervisors = cls.get_hypervisors_setup()
|
||||
available_hypervisors = set(
|
||||
hyp['hypervisor_hostname'] for hyp in hypervisors
|
||||
if hyp['state'] == 'up')
|
||||
available_services = set(
|
||||
service['host']
|
||||
for service in cls.get_compute_nodes_setup()
|
||||
if service['state'] == 'up')
|
||||
return (
|
||||
len(available_hypervisors) == len(available_services) and
|
||||
len(hypervisors) >= 2)
|
||||
except Exception as exc:
|
||||
LOG.exception(exc)
|
||||
return False
|
||||
|
||||
assert test_utils.call_until_true(
|
||||
func=_are_compute_nodes_setup,
|
||||
duration=600,
|
||||
sleep_for=2
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def rollback_compute_nodes_status(cls):
|
||||
current_compute_nodes_setup = cls.get_compute_nodes_setup()
|
||||
for cn_setup in current_compute_nodes_setup:
|
||||
cn_hostname = cn_setup.get('host')
|
||||
matching_cns = [
|
||||
cns for cns in cls.initial_compute_nodes_setup
|
||||
if cns.get('host') == cn_hostname
|
||||
]
|
||||
initial_cn_setup = matching_cns[0] # Should return a single result
|
||||
if cn_setup.get('status') != initial_cn_setup.get('status'):
|
||||
if initial_cn_setup.get('status') == 'enabled':
|
||||
rollback_func = cls.mgr.services_client.enable_service
|
||||
else:
|
||||
rollback_func = cls.mgr.services_client.disable_service
|
||||
rollback_func(binary='nova-compute', host=cn_hostname)
|
||||
|
||||
def _create_one_instance_per_host(self):
|
||||
"""Create 1 instance per compute node
|
||||
|
||||
This goes up to the min_compute_nodes threshold so that things don't
|
||||
get crazy if you have 1000 compute nodes but set min to 3.
|
||||
"""
|
||||
host_client = self.mgr.hosts_client
|
||||
all_hosts = host_client.list_hosts()['hosts']
|
||||
compute_nodes = [x for x in all_hosts if x['service'] == 'compute']
|
||||
|
||||
created_instances = []
|
||||
for _ in compute_nodes[:CONF.compute.min_compute_nodes]:
|
||||
# by getting to active state here, this means this has
|
||||
# landed on the host in question.
|
||||
created_instances.append(
|
||||
self.create_server(image_id=CONF.compute.image_ref,
|
||||
wait_until='ACTIVE', clients=self.mgr))
|
||||
return created_instances
|
||||
|
||||
def _pack_all_created_instances_on_one_host(self, instances):
|
||||
hypervisors = [
|
||||
hyp['hypervisor_hostname'] for hyp in self.get_hypervisors_setup()
|
||||
if hyp['state'] == 'up']
|
||||
node = hypervisors[0]
|
||||
for instance in instances:
|
||||
if instance.get('OS-EXT-SRV-ATTR:hypervisor_hostname') != node:
|
||||
self._migrate_server_to(instance['id'], node)
|
||||
|
||||
def test_execute_workload_stabilization(self):
|
||||
"""Execute an action plan using the workload_stabilization strategy"""
|
||||
self.addCleanup(self.rollback_compute_nodes_status)
|
||||
instances = self._create_one_instance_per_host()
|
||||
self._pack_all_created_instances_on_one_host(instances)
|
||||
|
||||
audit_parameters = {
|
||||
"metrics": ["cpu_util"],
|
||||
"thresholds": {"cpu_util": 0.2},
|
||||
"weights": {"cpu_util_weight": 1.0},
|
||||
"instance_metrics": {"cpu_util": "compute.node.cpu.percent"}}
|
||||
|
||||
_, goal = self.client.show_goal(self.GOAL)
|
||||
_, strategy = self.client.show_strategy("workload_stabilization")
|
||||
_, audit_template = self.create_audit_template(
|
||||
goal['uuid'], strategy=strategy['uuid'])
|
||||
_, audit = self.create_audit(
|
||||
audit_template['uuid'], parameters=audit_parameters)
|
||||
|
||||
try:
|
||||
self.assertTrue(test_utils.call_until_true(
|
||||
func=functools.partial(
|
||||
self.has_audit_finished, audit['uuid']),
|
||||
duration=600,
|
||||
sleep_for=2
|
||||
))
|
||||
except ValueError:
|
||||
self.fail("The audit has failed!")
|
||||
|
||||
_, finished_audit = self.client.show_audit(audit['uuid'])
|
||||
if finished_audit.get('state') in ('FAILED', 'CANCELLED'):
|
||||
self.fail("The audit ended in unexpected state: %s!" %
|
||||
finished_audit.get('state'))
|
||||
|
||||
_, action_plans = self.client.list_action_plans(
|
||||
audit_uuid=audit['uuid'])
|
||||
action_plan = action_plans['action_plans'][0]
|
||||
|
||||
_, action_plan = self.client.show_action_plan(action_plan['uuid'])
|
||||
_, action_list = self.client.list_actions(
|
||||
action_plan_uuid=action_plan["uuid"])
|
Loading…
x
Reference in New Issue
Block a user