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:
Alexander Chadin 2017-08-17 13:37:50 +03:00
parent 1b413f5536
commit 0c4b439c5e
33 changed files with 9 additions and 3303 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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")

View File

@ -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(),
)

View File

@ -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])]

View File

@ -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

View File

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

View File

@ -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

View File

@ -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"))

View File

@ -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'])

View File

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

View File

@ -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']])

View File

@ -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'])

View File

@ -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'])

View File

@ -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'])

View File

@ -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'])

View File

@ -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'])

View File

@ -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')

View File

@ -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

View File

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

View File

@ -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'))

View File

@ -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"))

View File

@ -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"])