summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexander Chadin <a.chadin@servionica.ru>2017-08-17 13:37:50 +0300
committerAlexander Chadin <a.chadin@servionica.ru>2017-08-21 17:41:56 +0300
commit0c4b439c5ea1206263f39c118daf6d2ff1422480 (patch)
tree6f5553e829695f87f0fd610be8c23127be127e48
parent1b413f55368101d1922d3f7b9a1635dc96979b82 (diff)
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
Notes
Notes (review): Code-Review+2: Vincent Françoise <Vincent.FRANCOISE@b-com.com> Workflow+1: Hidekazu Nakamura <hid-nakamura@vf.jp.nec.com> Verified+2: Jenkins Submitted-by: Jenkins Submitted-at: Thu, 24 Aug 2017 10:09:24 +0000 Reviewed-on: https://review.openstack.org/494472 Project: openstack/watcher Branch: refs/heads/master
-rw-r--r--doc/source/contributor/testing.rst10
-rw-r--r--setup.cfg5
-rw-r--r--watcher_tempest_plugin/README.rst158
-rw-r--r--watcher_tempest_plugin/__init__.py0
-rw-r--r--watcher_tempest_plugin/config.py23
-rw-r--r--watcher_tempest_plugin/infra_optim_clients.py42
-rw-r--r--watcher_tempest_plugin/plugin.py34
-rw-r--r--watcher_tempest_plugin/services/__init__.py0
-rw-r--r--watcher_tempest_plugin/services/infra_optim/__init__.py0
-rw-r--r--watcher_tempest_plugin/services/infra_optim/base.py211
-rw-r--r--watcher_tempest_plugin/services/infra_optim/v1/__init__.py0
-rw-r--r--watcher_tempest_plugin/services/infra_optim/v1/json/__init__.py0
-rw-r--r--watcher_tempest_plugin/services/infra_optim/v1/json/client.py331
-rw-r--r--watcher_tempest_plugin/tests/__init__.py0
-rw-r--r--watcher_tempest_plugin/tests/api/__init__.py0
-rw-r--r--watcher_tempest_plugin/tests/api/admin/__init__.py0
-rw-r--r--watcher_tempest_plugin/tests/api/admin/base.py259
-rw-r--r--watcher_tempest_plugin/tests/api/admin/test_action.py110
-rw-r--r--watcher_tempest_plugin/tests/api/admin/test_action_plan.py140
-rw-r--r--watcher_tempest_plugin/tests/api/admin/test_api_discovery.py47
-rw-r--r--watcher_tempest_plugin/tests/api/admin/test_audit.py221
-rw-r--r--watcher_tempest_plugin/tests/api/admin/test_audit_template.py226
-rw-r--r--watcher_tempest_plugin/tests/api/admin/test_goal.py66
-rw-r--r--watcher_tempest_plugin/tests/api/admin/test_scoring_engine.py65
-rw-r--r--watcher_tempest_plugin/tests/api/admin/test_service.py90
-rw-r--r--watcher_tempest_plugin/tests/api/admin/test_strategy.py69
-rw-r--r--watcher_tempest_plugin/tests/scenario/__init__.py0
-rw-r--r--watcher_tempest_plugin/tests/scenario/base.py185
-rw-r--r--watcher_tempest_plugin/tests/scenario/manager.py206
-rw-r--r--watcher_tempest_plugin/tests/scenario/test_execute_actuator.py340
-rw-r--r--watcher_tempest_plugin/tests/scenario/test_execute_basic_optim.py191
-rw-r--r--watcher_tempest_plugin/tests/scenario/test_execute_dummy_optim.py85
-rw-r--r--watcher_tempest_plugin/tests/scenario/test_execute_workload_balancing.py198
33 files changed, 9 insertions, 3303 deletions
diff --git a/doc/source/contributor/testing.rst b/doc/source/contributor/testing.rst
index ab0675f..afb0e72 100644
--- a/doc/source/contributor/testing.rst
+++ b/doc/source/contributor/testing.rst
@@ -47,4 +47,12 @@ When you're done, deactivate the virtualenv::
47 47
48 $ deactivate 48 $ deactivate
49 49
50.. include:: ../../../watcher_tempest_plugin/README.rst 50.. _tempest_tests:
51
52Tempest tests
53=============
54
55Tempest tests for Watcher has been migrated to the external repo
56`watcher-tempest-plugin`_.
57
58.. _watcher-tempest-plugin: https://github.com/openstack/watcher-tempest-plugin
diff --git a/setup.cfg b/setup.cfg
index 374a6e4..df2bee1 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -21,7 +21,6 @@ classifier =
21[files] 21[files]
22packages = 22packages =
23 watcher 23 watcher
24 watcher_tempest_plugin
25data_files = 24data_files =
26 etc/ = etc/* 25 etc/ = etc/*
27 26
@@ -40,9 +39,6 @@ console_scripts =
40 watcher-applier = watcher.cmd.applier:main 39 watcher-applier = watcher.cmd.applier:main
41 watcher-sync = watcher.cmd.sync:main 40 watcher-sync = watcher.cmd.sync:main
42 41
43tempest.test_plugins =
44 watcher_tests = watcher_tempest_plugin.plugin:WatcherTempestPlugin
45
46watcher.database.migration_backend = 42watcher.database.migration_backend =
47 sqlalchemy = watcher.db.sqlalchemy.migration 43 sqlalchemy = watcher.db.sqlalchemy.migration
48 44
@@ -103,7 +99,6 @@ autodoc_exclude_modules =
103 watcher.db.sqlalchemy.alembic.env 99 watcher.db.sqlalchemy.alembic.env
104 watcher.db.sqlalchemy.alembic.versions.* 100 watcher.db.sqlalchemy.alembic.versions.*
105 watcher.tests.* 101 watcher.tests.*
106 watcher_tempest_plugin.*
107 watcher.doc 102 watcher.doc
108 103
109 104
diff --git a/watcher_tempest_plugin/README.rst b/watcher_tempest_plugin/README.rst
deleted file mode 100644
index 1fd805f..0000000
--- a/watcher_tempest_plugin/README.rst
+++ /dev/null
@@ -1,158 +0,0 @@
1..
2 Except where otherwise noted, this document is licensed under Creative
3 Commons Attribution 3.0 License. You can view the license at:
4
5 https://creativecommons.org/licenses/by/3.0/
6
7.. _tempest_tests:
8
9Tempest tests
10=============
11
12The following procedure gets you started with Tempest testing but you can also
13refer to the `Tempest documentation`_ for more details.
14
15.. _Tempest documentation: https://docs.openstack.org/tempest/latest
16
17
18Tempest installation
19--------------------
20
21To install Tempest you can issue the following commands::
22
23 $ git clone https://github.com/openstack/tempest/
24 $ cd tempest/
25 $ pip install .
26
27The folder you are into now will be called ``<TEMPEST_DIR>`` from now onwards.
28
29Please note that although it is fully working outside a virtual environment, it
30is recommended to install within a `venv`.
31
32
33Watcher Tempest testing setup
34-----------------------------
35
36You can now install Watcher alongside it in development mode by issuing the
37following command::
38
39 $ pip install -e <WATCHER_SRC_DIR>
40
41Then setup a local working environment (here ``watcher-cloud``) for running
42Tempest for Watcher which shall contain the configuration for your OpenStack
43integration platform.
44
45In a virtual environment, you can do so by issuing the following command::
46
47 $ cd <TEMPEST_DIR>
48 $ tempest init watcher-cloud
49
50Otherwise, if you are not using a virtualenv::
51
52 $ cd <TEMPEST_DIR>
53 $ tempest init --config-dir ./etc watcher-cloud
54
55By default the configuration file is empty so before starting, you need to
56issue the following commands::
57
58 $ cd <TEMPEST_DIR>/watcher-cloud/etc
59 $ cp tempest.conf.sample tempest.conf
60
61At this point you need to edit the ``watcher-cloud/etc/tempest.conf``
62file as described in the `Tempest configuration guide`_.
63Shown below is a minimal configuration you need to set within your
64``tempest.conf`` configuration file which can get you started.
65
66For Keystone V3::
67
68 [identity]
69 uri_v3 = http://<KEYSTONE_PUBLIC_ENDPOINT_IP>:<KEYSTONE_PORT>/v3
70 auth_version = v3
71
72 [auth]
73 admin_username = <ADMIN_USERNAME>
74 admin_password = <ADMIN_PASSWORD>
75 admin_tenant_name = <ADMIN_TENANT_NAME>
76 admin_domain_name = <ADMIN_DOMAIN_NAME>
77
78 [identity-feature-enabled]
79 api_v2 = false
80 api_v3 = true
81
82For Keystone V2::
83
84 [identity]
85 uri = http://<KEYSTONE_PUBLIC_ENDPOINT_IP>:<KEYSTONE_PORT>/v2.0
86 auth_version = v2
87
88 [auth]
89 admin_tenant_name = <ADMIN_TENANT_NAME>
90 admin_username = <ADMIN_USERNAME>
91 admin_password = <ADMIN_PASSWORD>
92
93In both cases::
94
95 [network]
96 public_network_id = <PUBLIC_NETWORK_ID>
97
98You now have the minimum configuration for running Watcher Tempest tests on a
99single node.
100
101Since deploying Watcher with only a single compute node is not very useful, a
102few more configuration have to be set in your ``tempest.conf`` file in order to
103enable the execution of multi-node scenarios::
104
105 [compute]
106 # To indicate Tempest test that you have provided enough compute nodes
107 min_compute_nodes = 2
108
109 # Image UUID you can get using the "glance image-list" command
110 image_ref = <IMAGE_UUID>
111
112
113For more information, please refer to:
114
115- Keystone connection: https://docs.openstack.org/tempest/latest/configuration.html#keystone-connection-info
116- Dynamic Keystone Credentials: https://docs.openstack.org/tempest/latest/configuration.html#dynamic-credentials
117
118.. _virtual environment: http://docs.python-guide.org/en/latest/dev/virtualenvs/
119.. _Tempest configuration guide: http://docs.openstack.org/tempest/latest/configuration.html
120
121
122Watcher Tempest tests execution
123-------------------------------
124
125To list all Watcher Tempest cases, you can issue the following commands::
126
127 $ cd <TEMPEST_DIR>
128 $ testr list-tests watcher
129
130To run only these tests in Tempest, you can then issue these commands::
131
132 $ ./run_tempest.sh --config watcher-cloud/etc/tempest.conf -N -- watcher
133
134Or alternatively the following commands if you are::
135
136 $ cd <TEMPEST_DIR>/watcher-cloud
137 $ ../run_tempest.sh -N -- watcher
138
139To run a single test case, go to Tempest directory, then run with test case
140name, e.g.::
141
142 $ cd <TEMPEST_DIR>
143 $ ./run_tempest.sh --config watcher-cloud/etc/tempest.conf -N \
144 -- watcher_tempest_plugin.tests.api.admin.test_audit_template.TestCreateDeleteAuditTemplate.test_create_audit_template
145
146Alternatively, you can also run the Watcher Tempest plugin tests using tox. But
147before you can do so, you need to follow the Tempest explanation on running
148`tox with plugins`_. Then, run::
149
150 $ export TEMPEST_CONFIG_DIR=<TEMPEST_DIR>/watcher-cloud/etc/
151 $ tox -eall-plugin watcher
152
153.. _tox with plugins: https://docs.openstack.org/tempest/latest/plugin.html#notes-for-using-plugins-with-virtualenvs
154
155And, to run a specific test::
156
157 $ export TEMPEST_CONFIG_DIR=<TEMPEST_DIR>/watcher-cloud/etc/
158 $ tox -eall-plugin watcher_tempest_plugin.tests.api.admin.test_audit_template.TestCreateDeleteAuditTemplate.test_create_audit_template
diff --git a/watcher_tempest_plugin/__init__.py b/watcher_tempest_plugin/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/watcher_tempest_plugin/__init__.py
+++ /dev/null
diff --git a/watcher_tempest_plugin/config.py b/watcher_tempest_plugin/config.py
deleted file mode 100644
index 426399d..0000000
--- a/watcher_tempest_plugin/config.py
+++ /dev/null
@@ -1,23 +0,0 @@
1# -*- encoding: utf-8 -*-
2# Copyright (c) 2016 b<>com
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13# implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17from oslo_config import cfg
18
19
20service_option = cfg.BoolOpt("watcher",
21 default=True,
22 help="Whether or not watcher is expected to be "
23 "available")
diff --git a/watcher_tempest_plugin/infra_optim_clients.py b/watcher_tempest_plugin/infra_optim_clients.py
deleted file mode 100644
index edf2091..0000000
--- a/watcher_tempest_plugin/infra_optim_clients.py
+++ /dev/null
@@ -1,42 +0,0 @@
1# -*- encoding: utf-8 -*-
2# Copyright (c) 2016 b<>com
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13# implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17import abc
18
19import six
20from tempest import clients
21from tempest.common import credentials_factory as creds_factory
22from tempest import config
23
24from watcher_tempest_plugin.services.infra_optim.v1.json import client as ioc
25
26CONF = config.CONF
27
28
29@six.add_metaclass(abc.ABCMeta)
30class BaseManager(clients.Manager):
31
32 def __init__(self, credentials):
33 super(BaseManager, self).__init__(credentials)
34 self.io_client = ioc.InfraOptimClientJSON(
35 self.auth_provider, 'infra-optim', CONF.identity.region)
36
37
38class AdminManager(BaseManager):
39 def __init__(self):
40 super(AdminManager, self).__init__(
41 creds_factory.get_configured_admin_credentials(),
42 )
diff --git a/watcher_tempest_plugin/plugin.py b/watcher_tempest_plugin/plugin.py
deleted file mode 100644
index 560c544..0000000
--- a/watcher_tempest_plugin/plugin.py
+++ /dev/null
@@ -1,34 +0,0 @@
1# Licensed under the Apache License, Version 2.0 (the "License"); you may
2# not use this file except in compliance with the License. You may obtain
3# a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10# License for the specific language governing permissions and limitations
11# under the License.
12
13
14import os
15
16from tempest.test_discover import plugins
17
18from watcher_tempest_plugin import config as watcher_config
19
20
21class WatcherTempestPlugin(plugins.TempestPlugin):
22 def load_tests(self):
23 base_path = os.path.split(os.path.dirname(
24 os.path.abspath(__file__)))[0]
25 test_dir = "watcher_tempest_plugin/tests"
26 full_test_dir = os.path.join(base_path, test_dir)
27 return full_test_dir, base_path
28
29 def register_opts(self, conf):
30 conf.register_opt(watcher_config.service_option,
31 group='service_available')
32
33 def get_opt_lists(self):
34 return [('service_available', [watcher_config.service_option])]
diff --git a/watcher_tempest_plugin/services/__init__.py b/watcher_tempest_plugin/services/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/watcher_tempest_plugin/services/__init__.py
+++ /dev/null
diff --git a/watcher_tempest_plugin/services/infra_optim/__init__.py b/watcher_tempest_plugin/services/infra_optim/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/watcher_tempest_plugin/services/infra_optim/__init__.py
+++ /dev/null
diff --git a/watcher_tempest_plugin/services/infra_optim/base.py b/watcher_tempest_plugin/services/infra_optim/base.py
deleted file mode 100644
index d248774..0000000
--- a/watcher_tempest_plugin/services/infra_optim/base.py
+++ /dev/null
@@ -1,211 +0,0 @@
1# -*- encoding: utf-8 -*-
2# Copyright (c) 2016 b<>com
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13# implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17import abc
18import functools
19
20import six
21import six.moves.urllib.parse as urlparse
22
23from tempest.lib.common import rest_client
24
25
26def handle_errors(f):
27 """A decorator that allows to ignore certain types of errors."""
28
29 @functools.wraps(f)
30 def wrapper(*args, **kwargs):
31 param_name = 'ignore_errors'
32 ignored_errors = kwargs.get(param_name, tuple())
33
34 if param_name in kwargs:
35 del kwargs[param_name]
36
37 try:
38 return f(*args, **kwargs)
39 except ignored_errors:
40 # Silently ignore errors
41 pass
42
43 return wrapper
44
45
46@six.add_metaclass(abc.ABCMeta)
47class BaseInfraOptimClient(rest_client.RestClient):
48 """Base Tempest REST client for Watcher API."""
49
50 URI_PREFIX = ''
51
52 @abc.abstractmethod
53 def serialize(self, object_dict):
54 """Serialize an Watcher object."""
55 raise NotImplementedError()
56
57 @abc.abstractmethod
58 def deserialize(self, object_str):
59 """Deserialize an Watcher object."""
60 raise NotImplementedError()
61
62 def _get_uri(self, resource_name, uuid=None, permanent=False):
63 """Get URI for a specific resource or object.
64
65 :param resource_name: The name of the REST resource, e.g., 'audits'.
66 :param uuid: The unique identifier of an object in UUID format.
67 :return: Relative URI for the resource or object.
68 """
69
70 prefix = self.URI_PREFIX if not permanent else ''
71
72 return '{pref}/{res}{uuid}'.format(pref=prefix,
73 res=resource_name,
74 uuid='/%s' % uuid if uuid else '')
75
76 def _make_patch(self, allowed_attributes, **kw):
77 """Create a JSON patch according to RFC 6902.
78
79 :param allowed_attributes: An iterable object that contains a set of
80 allowed attributes for an object.
81 :param **kw: Attributes and new values for them.
82 :return: A JSON path that sets values of the specified attributes to
83 the new ones.
84 """
85
86 def get_change(kw, path='/'):
87 for name, value in kw.items():
88 if isinstance(value, dict):
89 for ch in get_change(value, path + '%s/' % name):
90 yield ch
91 else:
92 if value is None:
93 yield {'path': path + name,
94 'op': 'remove'}
95 else:
96 yield {'path': path + name,
97 'value': value,
98 'op': 'replace'}
99
100 patch = [ch for ch in get_change(kw)
101 if ch['path'].lstrip('/') in allowed_attributes]
102
103 return patch
104
105 def _list_request(self, resource, permanent=False, **kwargs):
106 """Get the list of objects of the specified type.
107
108 :param resource: The name of the REST resource, e.g., 'audits'.
109 "param **kw: Parameters for the request.
110 :return: A tuple with the server response and deserialized JSON list
111 of objects
112 """
113
114 uri = self._get_uri(resource, permanent=permanent)
115 if kwargs:
116 uri += "?%s" % urlparse.urlencode(kwargs)
117
118 resp, body = self.get(uri)
119 self.expected_success(200, int(resp['status']))
120
121 return resp, self.deserialize(body)
122
123 def _show_request(self, resource, uuid, permanent=False, **kwargs):
124 """Gets a specific object of the specified type.
125
126 :param uuid: Unique identifier of the object in UUID format.
127 :return: Serialized object as a dictionary.
128 """
129
130 if 'uri' in kwargs:
131 uri = kwargs['uri']
132 else:
133 uri = self._get_uri(resource, uuid=uuid, permanent=permanent)
134 resp, body = self.get(uri)
135 self.expected_success(200, int(resp['status']))
136
137 return resp, self.deserialize(body)
138
139 def _create_request(self, resource, object_dict):
140 """Create an object of the specified type.
141
142 :param resource: The name of the REST resource, e.g., 'audits'.
143 :param object_dict: A Python dict that represents an object of the
144 specified type.
145 :return: A tuple with the server response and the deserialized created
146 object.
147 """
148
149 body = self.serialize(object_dict)
150 uri = self._get_uri(resource)
151
152 resp, body = self.post(uri, body=body)
153 self.expected_success(201, int(resp['status']))
154
155 return resp, self.deserialize(body)
156
157 def _delete_request(self, resource, uuid):
158 """Delete specified object.
159
160 :param resource: The name of the REST resource, e.g., 'audits'.
161 :param uuid: The unique identifier of an object in UUID format.
162 :return: A tuple with the server response and the response body.
163 """
164
165 uri = self._get_uri(resource, uuid)
166
167 resp, body = self.delete(uri)
168 self.expected_success(204, int(resp['status']))
169 return resp, body
170
171 def _patch_request(self, resource, uuid, patch_object):
172 """Update specified object with JSON-patch.
173
174 :param resource: The name of the REST resource, e.g., 'audits'.
175 :param uuid: The unique identifier of an object in UUID format.
176 :return: A tuple with the server response and the serialized patched
177 object.
178 """
179
180 uri = self._get_uri(resource, uuid)
181 patch_body = self.serialize(patch_object)
182
183 resp, body = self.patch(uri, body=patch_body)
184 self.expected_success(200, int(resp['status']))
185 return resp, self.deserialize(body)
186
187 @handle_errors
188 def get_api_description(self):
189 """Retrieves all versions of the Watcher API."""
190
191 return self._list_request('', permanent=True)
192
193 @handle_errors
194 def get_version_description(self, version='v1'):
195 """Retrieves the description of the API.
196
197 :param version: The version of the API. Default: 'v1'.
198 :return: Serialized description of API resources.
199 """
200
201 return self._list_request(version, permanent=True)
202
203 def _put_request(self, resource, put_object):
204 """Update specified object with JSON-patch."""
205
206 uri = self._get_uri(resource)
207 put_body = self.serialize(put_object)
208
209 resp, body = self.put(uri, body=put_body)
210 self.expected_success(202, int(resp['status']))
211 return resp, body
diff --git a/watcher_tempest_plugin/services/infra_optim/v1/__init__.py b/watcher_tempest_plugin/services/infra_optim/v1/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/watcher_tempest_plugin/services/infra_optim/v1/__init__.py
+++ /dev/null
diff --git a/watcher_tempest_plugin/services/infra_optim/v1/json/__init__.py b/watcher_tempest_plugin/services/infra_optim/v1/json/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/watcher_tempest_plugin/services/infra_optim/v1/json/__init__.py
+++ /dev/null
diff --git a/watcher_tempest_plugin/services/infra_optim/v1/json/client.py b/watcher_tempest_plugin/services/infra_optim/v1/json/client.py
deleted file mode 100644
index 2ee27f5..0000000
--- a/watcher_tempest_plugin/services/infra_optim/v1/json/client.py
+++ /dev/null
@@ -1,331 +0,0 @@
1# -*- encoding: utf-8 -*-
2# Copyright (c) 2016 b<>com
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13# implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17from oslo_serialization import jsonutils
18from watcher.common import utils
19from watcher_tempest_plugin.services.infra_optim import base
20
21
22class InfraOptimClientJSON(base.BaseInfraOptimClient):
23 """Base Tempest REST client for Watcher API v1."""
24
25 URI_PREFIX = 'v1'
26
27 def serialize(self, object_dict):
28 """Serialize an Watcher object."""
29 return jsonutils.dumps(object_dict)
30
31 def deserialize(self, object_str):
32 """Deserialize an Watcher object."""
33 return jsonutils.loads(object_str.decode('utf-8'))
34
35 # ### AUDIT TEMPLATES ### #
36
37 @base.handle_errors
38 def list_audit_templates(self, **kwargs):
39 """List all existing audit templates."""
40 return self._list_request('audit_templates', **kwargs)
41
42 @base.handle_errors
43 def list_audit_templates_detail(self, **kwargs):
44 """Lists details of all existing audit templates."""
45 return self._list_request('/audit_templates/detail', **kwargs)
46
47 @base.handle_errors
48 def show_audit_template(self, audit_template_uuid):
49 """Gets a specific audit template.
50
51 :param audit_template_uuid: Unique identifier of the audit template
52 :return: Serialized audit template as a dictionary.
53 """
54 return self._show_request('audit_templates', audit_template_uuid)
55
56 @base.handle_errors
57 def create_audit_template(self, **kwargs):
58 """Creates an audit template with the specified parameters.
59
60 :param name: The name of the audit template.
61 :param description: The description of the audit template.
62 :param goal_uuid: The related Goal UUID associated.
63 :param strategy_uuid: The related Strategy UUID associated.
64 :param audit_scope: Scope the audit should apply to.
65 :return: A tuple with the server response and the created audit
66 template.
67 """
68
69 parameters = {k: v for k, v in kwargs.items() if v is not None}
70 # This name is unique to avoid the DB unique constraint on names
71 unique_name = 'Tempest Audit Template %s' % utils.generate_uuid()
72
73 audit_template = {
74 'name': parameters.get('name', unique_name),
75 'description': parameters.get('description'),
76 'goal': parameters.get('goal'),
77 'strategy': parameters.get('strategy'),
78 'scope': parameters.get('scope', []),
79 }
80
81 return self._create_request('audit_templates', audit_template)
82
83 @base.handle_errors
84 def delete_audit_template(self, audit_template_uuid):
85 """Deletes an audit template having the specified UUID.
86
87 :param audit_template_uuid: The unique identifier of the audit template
88 :return: A tuple with the server response and the response body.
89 """
90
91 return self._delete_request('audit_templates', audit_template_uuid)
92
93 @base.handle_errors
94 def update_audit_template(self, audit_template_uuid, patch):
95 """Update the specified audit template.
96
97 :param audit_template_uuid: The unique identifier of the audit template
98 :param patch: List of dicts representing json patches.
99 :return: A tuple with the server response and the updated audit
100 template.
101 """
102
103 return self._patch_request('audit_templates',
104 audit_template_uuid, patch)
105
106 # ### AUDITS ### #
107
108 @base.handle_errors
109 def list_audits(self, **kwargs):
110 """List all existing audit templates."""
111 return self._list_request('audits', **kwargs)
112
113 @base.handle_errors
114 def list_audits_detail(self, **kwargs):
115 """Lists details of all existing audit templates."""
116 return self._list_request('/audits/detail', **kwargs)
117
118 @base.handle_errors
119 def show_audit(self, audit_uuid):
120 """Gets a specific audit template.
121
122 :param audit_uuid: Unique identifier of the audit template
123 :return: Serialized audit template as a dictionary
124 """
125 return self._show_request('audits', audit_uuid)
126
127 @base.handle_errors
128 def create_audit(self, audit_template_uuid, **kwargs):
129 """Create an audit with the specified parameters
130
131 :param audit_template_uuid: Audit template ID used by the audit
132 :return: A tuple with the server response and the created audit
133 """
134 audit = {'audit_template_uuid': audit_template_uuid}
135 audit.update(kwargs)
136 if not audit['state']:
137 del audit['state']
138
139 return self._create_request('audits', audit)
140
141 @base.handle_errors
142 def delete_audit(self, audit_uuid):
143 """Deletes an audit having the specified UUID
144
145 :param audit_uuid: The unique identifier of the audit
146 :return: A tuple with the server response and the response body
147 """
148
149 return self._delete_request('audits', audit_uuid)
150
151 @base.handle_errors
152 def update_audit(self, audit_uuid, patch):
153 """Update the specified audit.
154
155 :param audit_uuid: The unique identifier of the audit
156 :param patch: List of dicts representing json patches.
157 :return: Tuple with the server response and the updated audit
158 """
159
160 return self._patch_request('audits', audit_uuid, patch)
161
162 # ### ACTION PLANS ### #
163
164 @base.handle_errors
165 def list_action_plans(self, **kwargs):
166 """List all existing action plan"""
167 return self._list_request('action_plans', **kwargs)
168
169 @base.handle_errors
170 def list_action_plans_detail(self, **kwargs):
171 """Lists details of all existing action plan"""
172 return self._list_request('/action_plans/detail', **kwargs)
173
174 @base.handle_errors
175 def show_action_plan(self, action_plan_uuid):
176 """Gets a specific action plan
177
178 :param action_plan_uuid: Unique identifier of the action plan
179 :return: Serialized action plan as a dictionary
180 """
181 return self._show_request('/action_plans', action_plan_uuid)
182
183 @base.handle_errors
184 def delete_action_plan(self, action_plan_uuid):
185 """Deletes an action plan having the specified UUID
186
187 :param action_plan_uuid: The unique identifier of the action_plan
188 :return: A tuple with the server response and the response body
189 """
190
191 return self._delete_request('/action_plans', action_plan_uuid)
192
193 @base.handle_errors
194 def delete_action_plans_by_audit(self, audit_uuid):
195 """Deletes an action plan having the specified UUID
196
197 :param audit_uuid: The unique identifier of the related Audit
198 """
199
200 action_plans = self.list_action_plans(audit_uuid=audit_uuid)[1]
201
202 for action_plan in action_plans:
203 self.delete_action_plan(action_plan['uuid'])
204
205 @base.handle_errors
206 def update_action_plan(self, action_plan_uuid, patch):
207 """Update the specified action plan
208
209 :param action_plan_uuid: The unique identifier of the action_plan
210 :param patch: List of dicts representing json patches.
211 :return: Tuple with the server response and the updated action_plan
212 """
213
214 return self._patch_request('/action_plans', action_plan_uuid, patch)
215
216 @base.handle_errors
217 def start_action_plan(self, action_plan_uuid):
218 """Start the specified action plan
219
220 :param action_plan_uuid: The unique identifier of the action_plan
221 :return: Tuple with the server response and the updated action_plan
222 """
223
224 return self._patch_request(
225 '/action_plans', action_plan_uuid,
226 [{'path': '/state', 'op': 'replace', 'value': 'PENDING'}])
227
228 # ### GOALS ### #
229
230 @base.handle_errors
231 def list_goals(self, **kwargs):
232 """List all existing goals"""
233 return self._list_request('/goals', **kwargs)
234
235 @base.handle_errors
236 def list_goals_detail(self, **kwargs):
237 """Lists details of all existing goals"""
238 return self._list_request('/goals/detail', **kwargs)
239
240 @base.handle_errors
241 def show_goal(self, goal):
242 """Gets a specific goal
243
244 :param goal: UUID or Name of the goal
245 :return: Serialized goal as a dictionary
246 """
247 return self._show_request('/goals', goal)
248
249 # ### ACTIONS ### #
250
251 @base.handle_errors
252 def list_actions(self, **kwargs):
253 """List all existing actions"""
254 return self._list_request('/actions', **kwargs)
255
256 @base.handle_errors
257 def list_actions_detail(self, **kwargs):
258 """Lists details of all existing actions"""
259 return self._list_request('/actions/detail', **kwargs)
260
261 @base.handle_errors
262 def show_action(self, action_uuid):
263 """Gets a specific action
264
265 :param action_uuid: Unique identifier of the action
266 :return: Serialized action as a dictionary
267 """
268 return self._show_request('/actions', action_uuid)
269
270 # ### STRATEGIES ### #
271
272 @base.handle_errors
273 def list_strategies(self, **kwargs):
274 """List all existing strategies"""
275 return self._list_request('/strategies', **kwargs)
276
277 @base.handle_errors
278 def list_strategies_detail(self, **kwargs):
279 """Lists details of all existing strategies"""
280 return self._list_request('/strategies/detail', **kwargs)
281
282 @base.handle_errors
283 def show_strategy(self, strategy):
284 """Gets a specific strategy
285
286 :param strategy_id: Name of the strategy
287 :return: Serialized strategy as a dictionary
288 """
289 return self._show_request('/strategies', strategy)
290
291 # ### SCORING ENGINE ### #
292
293 @base.handle_errors
294 def list_scoring_engines(self, **kwargs):
295 """List all existing scoring_engines"""
296 return self._list_request('/scoring_engines', **kwargs)
297
298 @base.handle_errors
299 def list_scoring_engines_detail(self, **kwargs):
300 """Lists details of all existing scoring_engines"""
301 return self._list_request('/scoring_engines/detail', **kwargs)
302
303 @base.handle_errors
304 def show_scoring_engine(self, scoring_engine):
305 """Gets a specific scoring_engine
306
307 :param scoring_engine: UUID or Name of the scoring_engine
308 :return: Serialized scoring_engine as a dictionary
309 """
310 return self._show_request('/scoring_engines', scoring_engine)
311
312 # ### SERVICES ### #
313
314 @base.handle_errors
315 def list_services(self, **kwargs):
316 """List all existing services"""
317 return self._list_request('/services', **kwargs)
318
319 @base.handle_errors
320 def list_services_detail(self, **kwargs):
321 """Lists details of all existing services"""
322 return self._list_request('/services/detail', **kwargs)
323
324 @base.handle_errors
325 def show_service(self, service):
326 """Gets a specific service
327
328 :param service: Name of the strategy
329 :return: Serialized strategy as a dictionary
330 """
331 return self._show_request('/services', service)
diff --git a/watcher_tempest_plugin/tests/__init__.py b/watcher_tempest_plugin/tests/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/watcher_tempest_plugin/tests/__init__.py
+++ /dev/null
diff --git a/watcher_tempest_plugin/tests/api/__init__.py b/watcher_tempest_plugin/tests/api/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/watcher_tempest_plugin/tests/api/__init__.py
+++ /dev/null
diff --git a/watcher_tempest_plugin/tests/api/admin/__init__.py b/watcher_tempest_plugin/tests/api/admin/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/watcher_tempest_plugin/tests/api/admin/__init__.py
+++ /dev/null
diff --git a/watcher_tempest_plugin/tests/api/admin/base.py b/watcher_tempest_plugin/tests/api/admin/base.py
deleted file mode 100644
index 63cdb83..0000000
--- a/watcher_tempest_plugin/tests/api/admin/base.py
+++ /dev/null
@@ -1,259 +0,0 @@
1# -*- encoding: utf-8 -*-
2# Copyright (c) 2016 b<>com
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13# implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17import functools
18
19from tempest.lib.common.utils import data_utils
20from tempest.lib.common.utils import test_utils
21from tempest import test
22
23from watcher_tempest_plugin import infra_optim_clients as clients
24
25
26class BaseInfraOptimTest(test.BaseTestCase):
27 """Base class for Infrastructure Optimization API tests."""
28
29 # States where the object is waiting for some event to perform a transition
30 IDLE_STATES = ('RECOMMENDED',
31 'FAILED',
32 'SUCCEEDED',
33 'CANCELLED',
34 'SUSPENDED')
35 # States where the object can only be DELETED (end of its life-cycle)
36 FINISHED_STATES = ('FAILED',
37 'SUCCEEDED',
38 'CANCELLED',
39 'SUPERSEDED')
40
41 @classmethod
42 def setup_credentials(cls):
43 super(BaseInfraOptimTest, cls).setup_credentials()
44 cls.mgr = clients.AdminManager()
45
46 @classmethod
47 def setup_clients(cls):
48 super(BaseInfraOptimTest, cls).setup_clients()
49 cls.client = cls.mgr.io_client
50
51 @classmethod
52 def resource_setup(cls):
53 super(BaseInfraOptimTest, cls).resource_setup()
54
55 # Set of all created audit templates UUIDs
56 cls.created_audit_templates = set()
57 # Set of all created audit UUIDs
58 cls.created_audits = set()
59 # Set of all created audit UUIDs. We use it to build the list of
60 # action plans to delete (including potential orphan one(s))
61 cls.created_action_plans_audit_uuids = set()
62
63 @classmethod
64 def resource_cleanup(cls):
65 """Ensure that all created objects get destroyed."""
66 try:
67 action_plans_to_be_deleted = set()
68 # Phase 1: Make sure all objects are in an idle state
69 for audit_uuid in cls.created_audits:
70 test_utils.call_until_true(
71 func=functools.partial(
72 cls.is_audit_idle, audit_uuid),
73 duration=30,
74 sleep_for=.5
75 )
76
77 for audit_uuid in cls.created_action_plans_audit_uuids:
78 _, action_plans = cls.client.list_action_plans(
79 audit_uuid=audit_uuid)
80 action_plans_to_be_deleted.update(
81 ap['uuid'] for ap in action_plans['action_plans'])
82
83 for action_plan in action_plans['action_plans']:
84 try:
85 test_utils.call_until_true(
86 func=functools.partial(
87 cls.is_action_plan_idle, action_plan['uuid']),
88 duration=30,
89 sleep_for=.5
90 )
91 except Exception:
92 action_plans_to_be_deleted.remove(
93 action_plan['uuid'])
94
95 # Phase 2: Delete them all
96 for action_plan_uuid in action_plans_to_be_deleted:
97 cls.delete_action_plan(action_plan_uuid)
98
99 for audit_uuid in cls.created_audits.copy():
100 cls.delete_audit(audit_uuid)
101
102 for audit_template_uuid in cls.created_audit_templates.copy():
103 cls.delete_audit_template(audit_template_uuid)
104
105 finally:
106 super(BaseInfraOptimTest, cls).resource_cleanup()
107
108 def validate_self_link(self, resource, uuid, link):
109 """Check whether the given self link formatted correctly."""
110 expected_link = "{base}/{pref}/{res}/{uuid}".format(
111 base=self.client.base_url,
112 pref=self.client.URI_PREFIX,
113 res=resource,
114 uuid=uuid
115 )
116 self.assertEqual(expected_link, link)
117
118 def assert_expected(self, expected, actual,
119 keys=('created_at', 'updated_at', 'deleted_at')):
120 # Check if not expected keys/values exists in actual response body
121 for key, value in expected.items():
122 if key not in keys:
123 self.assertIn(key, actual)
124 self.assertEqual(value, actual[key])
125
126 # ### AUDIT TEMPLATES ### #
127
128 @classmethod
129 def create_audit_template(cls, goal, name=None, description=None,
130 strategy=None, scope=None):
131 """Wrapper utility for creating a test audit template
132
133 :param goal: Goal UUID or name related to the audit template.
134 :param name: The name of the audit template. Default: My Audit Template
135 :param description: The description of the audit template.
136 :param strategy: Strategy UUID or name related to the audit template.
137 :param scope: Scope that will be applied on all derived audits.
138 :return: A tuple with The HTTP response and its body
139 """
140 description = description or data_utils.rand_name(
141 'test-audit_template')
142 resp, body = cls.client.create_audit_template(
143 name=name, description=description,
144 goal=goal, strategy=strategy, scope=scope)
145
146 cls.created_audit_templates.add(body['uuid'])
147
148 return resp, body
149
150 @classmethod
151 def delete_audit_template(cls, uuid):
152 """Deletes a audit_template having the specified UUID
153
154 :param uuid: The unique identifier of the audit template
155 :return: Server response
156 """
157 resp, _ = cls.client.delete_audit_template(uuid)
158
159 if uuid in cls.created_audit_templates:
160 cls.created_audit_templates.remove(uuid)
161
162 return resp
163
164 # ### AUDITS ### #
165
166 @classmethod
167 def create_audit(cls, audit_template_uuid, audit_type='ONESHOT',
168 state=None, interval=None, parameters=None):
169 """Wrapper utility for creating a test audit
170
171 :param audit_template_uuid: Audit Template UUID this audit will use
172 :param audit_type: Audit type (either ONESHOT or CONTINUOUS)
173 :param state: Audit state (str)
174 :param interval: Audit interval in seconds or cron syntax (str)
175 :param parameters: list of execution parameters
176 :return: A tuple with The HTTP response and its body
177 """
178 resp, body = cls.client.create_audit(
179 audit_template_uuid=audit_template_uuid, audit_type=audit_type,
180 state=state, interval=interval, parameters=parameters)
181
182 cls.created_audits.add(body['uuid'])
183 cls.created_action_plans_audit_uuids.add(body['uuid'])
184
185 return resp, body
186
187 @classmethod
188 def delete_audit(cls, audit_uuid):
189 """Deletes an audit having the specified UUID
190
191 :param audit_uuid: The unique identifier of the audit.
192 :return: the HTTP response
193 """
194 resp, _ = cls.client.delete_audit(audit_uuid)
195
196 if audit_uuid in cls.created_audits:
197 cls.created_audits.remove(audit_uuid)
198
199 return resp
200
201 @classmethod
202 def has_audit_succeeded(cls, audit_uuid):
203 _, audit = cls.client.show_audit(audit_uuid)
204 return audit.get('state') == 'SUCCEEDED'
205
206 @classmethod
207 def has_audit_finished(cls, audit_uuid):
208 _, audit = cls.client.show_audit(audit_uuid)
209 return audit.get('state') in cls.FINISHED_STATES
210
211 @classmethod
212 def is_audit_idle(cls, audit_uuid):
213 _, audit = cls.client.show_audit(audit_uuid)
214 return audit.get('state') in cls.IDLE_STATES
215
216 # ### ACTION PLANS ### #
217
218 @classmethod
219 def create_action_plan(cls, audit_template_uuid, **audit_kwargs):
220 """Wrapper utility for creating a test action plan
221
222 :param audit_template_uuid: Audit template UUID to use
223 :param audit_kwargs: Dict of audit properties to set
224 :return: The action plan as dict
225 """
226 _, audit = cls.create_audit(audit_template_uuid, **audit_kwargs)
227 audit_uuid = audit['uuid']
228
229 assert test_utils.call_until_true(
230 func=functools.partial(cls.has_audit_finished, audit_uuid),
231 duration=30,
232 sleep_for=.5
233 )
234
235 _, action_plans = cls.client.list_action_plans(audit_uuid=audit_uuid)
236 if len(action_plans['action_plans']) == 0:
237 return
238
239 return action_plans['action_plans'][0]
240
241 @classmethod
242 def delete_action_plan(cls, action_plan_uuid):
243 """Deletes an action plan having the specified UUID
244
245 :param action_plan_uuid: The unique identifier of the action plan.
246 :return: the HTTP response
247 """
248 resp, _ = cls.client.delete_action_plan(action_plan_uuid)
249
250 if action_plan_uuid in cls.created_action_plans_audit_uuids:
251 cls.created_action_plans_audit_uuids.remove(action_plan_uuid)
252
253 return resp
254
255 @classmethod
256 def is_action_plan_idle(cls, action_plan_uuid):
257 """This guard makes sure your action plan is not running"""
258 _, action_plan = cls.client.show_action_plan(action_plan_uuid)
259 return action_plan.get('state') in cls.IDLE_STATES
diff --git a/watcher_tempest_plugin/tests/api/admin/test_action.py b/watcher_tempest_plugin/tests/api/admin/test_action.py
deleted file mode 100644
index 3fa2d94..0000000
--- a/watcher_tempest_plugin/tests/api/admin/test_action.py
+++ /dev/null
@@ -1,110 +0,0 @@
1# -*- encoding: utf-8 -*-
2# Copyright (c) 2016 b<>com
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13# implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17from __future__ import unicode_literals
18
19import collections
20import functools
21
22from tempest.lib.common.utils import test_utils
23from tempest.lib import decorators
24
25from watcher_tempest_plugin.tests.api.admin import base
26
27
28class TestShowListAction(base.BaseInfraOptimTest):
29 """Tests for actions"""
30
31 @classmethod
32 def resource_setup(cls):
33 super(TestShowListAction, cls).resource_setup()
34 _, cls.goal = cls.client.show_goal("DUMMY")
35 _, cls.audit_template = cls.create_audit_template(cls.goal['uuid'])
36 _, cls.audit = cls.create_audit(cls.audit_template['uuid'])
37
38 assert test_utils.call_until_true(
39 func=functools.partial(cls.has_audit_finished, cls.audit['uuid']),
40 duration=30,
41 sleep_for=.5
42 )
43 _, action_plans = cls.client.list_action_plans(
44 audit_uuid=cls.audit['uuid'])
45 cls.action_plan = action_plans['action_plans'][0]
46
47 @decorators.attr(type='smoke')
48 def test_show_one_action(self):
49 _, body = self.client.list_actions(
50 action_plan_uuid=self.action_plan["uuid"])
51 actions = body['actions']
52
53 _, action = self.client.show_action(actions[0]["uuid"])
54
55 self.assertEqual(self.action_plan["uuid"], action['action_plan_uuid'])
56 self.assertEqual("PENDING", action['state'])
57
58 @decorators.attr(type='smoke')
59 def test_show_action_with_links(self):
60 _, body = self.client.list_actions(
61 action_plan_uuid=self.action_plan["uuid"])
62 actions = body['actions']
63
64 _, action = self.client.show_action(actions[0]["uuid"])
65
66 self.assertIn('links', action.keys())
67 self.assertEqual(2, len(action['links']))
68 self.assertIn(action['uuid'], action['links'][0]['href'])
69
70 @decorators.attr(type="smoke")
71 def test_list_actions(self):
72 _, body = self.client.list_actions()
73
74 # Verify self links.
75 for action in body['actions']:
76 self.validate_self_link('actions', action['uuid'],
77 action['links'][0]['href'])
78
79 @decorators.attr(type="smoke")
80 def test_list_actions_by_action_plan(self):
81 _, body = self.client.list_actions(
82 action_plan_uuid=self.action_plan["uuid"])
83
84 for item in body['actions']:
85 self.assertEqual(self.action_plan["uuid"],
86 item['action_plan_uuid'])
87
88 action_counter = collections.Counter(
89 act['action_type'] for act in body['actions'])
90
91 # A dummy strategy generates 2 "nop" actions and 1 "sleep" action
92 self.assertEqual(3, len(body['actions']))
93 self.assertEqual(2, action_counter.get("nop"))
94 self.assertEqual(1, action_counter.get("sleep"))
95
96 @decorators.attr(type="smoke")
97 def test_list_actions_by_audit(self):
98 _, body = self.client.list_actions(audit_uuid=self.audit["uuid"])
99
100 for item in body['actions']:
101 self.assertEqual(self.action_plan["uuid"],
102 item['action_plan_uuid'])
103
104 action_counter = collections.Counter(
105 act['action_type'] for act in body['actions'])
106
107 # A dummy strategy generates 2 "nop" actions and 1 "sleep" action
108 self.assertEqual(3, len(body['actions']))
109 self.assertEqual(2, action_counter.get("nop"))
110 self.assertEqual(1, action_counter.get("sleep"))
diff --git a/watcher_tempest_plugin/tests/api/admin/test_action_plan.py b/watcher_tempest_plugin/tests/api/admin/test_action_plan.py
deleted file mode 100644
index 442ac42..0000000
--- a/watcher_tempest_plugin/tests/api/admin/test_action_plan.py
+++ /dev/null
@@ -1,140 +0,0 @@
1# -*- encoding: utf-8 -*-
2# Copyright (c) 2016 b<>com
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13# implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17from __future__ import unicode_literals
18
19import functools
20
21from tempest.lib.common.utils import test_utils
22from tempest.lib import decorators
23from tempest.lib import exceptions
24
25from watcher_tempest_plugin.tests.api.admin import base
26
27
28class TestCreateDeleteExecuteActionPlan(base.BaseInfraOptimTest):
29 """Tests for action plans"""
30
31 @decorators.attr(type='smoke')
32 def test_create_action_plan(self):
33 _, goal = self.client.show_goal("dummy")
34 _, audit_template = self.create_audit_template(goal['uuid'])
35 _, audit = self.create_audit(audit_template['uuid'])
36
37 self.assertTrue(test_utils.call_until_true(
38 func=functools.partial(self.has_audit_finished, audit['uuid']),
39 duration=30,
40 sleep_for=.5
41 ))
42 _, action_plans = self.client.list_action_plans(
43 audit_uuid=audit['uuid'])
44 action_plan = action_plans['action_plans'][0]
45
46 _, action_plan = self.client.show_action_plan(action_plan['uuid'])
47
48 self.assertEqual(audit['uuid'], action_plan['audit_uuid'])
49 self.assertEqual('RECOMMENDED', action_plan['state'])
50
51 @decorators.attr(type='smoke')
52 def test_delete_action_plan(self):
53 _, goal = self.client.show_goal("dummy")
54 _, audit_template = self.create_audit_template(goal['uuid'])
55 _, audit = self.create_audit(audit_template['uuid'])
56
57 self.assertTrue(test_utils.call_until_true(
58 func=functools.partial(self.has_audit_finished, audit['uuid']),
59 duration=30,
60 sleep_for=.5
61 ))
62 _, action_plans = self.client.list_action_plans(
63 audit_uuid=audit['uuid'])
64 action_plan = action_plans['action_plans'][0]
65
66 _, action_plan = self.client.show_action_plan(action_plan['uuid'])
67
68 self.client.delete_action_plan(action_plan['uuid'])
69
70 self.assertRaises(exceptions.NotFound, self.client.show_action_plan,
71 action_plan['uuid'])
72
73
74class TestShowListActionPlan(base.BaseInfraOptimTest):
75 """Tests for action_plan."""
76
77 @classmethod
78 def resource_setup(cls):
79 super(TestShowListActionPlan, cls).resource_setup()
80 _, cls.goal = cls.client.show_goal("dummy")
81 _, cls.audit_template = cls.create_audit_template(cls.goal['uuid'])
82 _, cls.audit = cls.create_audit(cls.audit_template['uuid'])
83
84 assert test_utils.call_until_true(
85 func=functools.partial(cls.has_audit_finished, cls.audit['uuid']),
86 duration=30,
87 sleep_for=.5
88 )
89 _, action_plans = cls.client.list_action_plans(
90 audit_uuid=cls.audit['uuid'])
91 if len(action_plans['action_plans']) > 0:
92 cls.action_plan = action_plans['action_plans'][0]
93
94 @decorators.attr(type='smoke')
95 def test_show_action_plan(self):
96 _, action_plan = self.client.show_action_plan(
97 self.action_plan['uuid'])
98
99 self.assert_expected(self.action_plan, action_plan)
100
101 @decorators.attr(type='smoke')
102 def test_show_action_plan_detail(self):
103 _, action_plans = self.client.list_action_plans_detail(
104 audit_uuid=self.audit['uuid'])
105
106 action_plan = action_plans['action_plans'][0]
107
108 self.assert_expected(self.action_plan, action_plan)
109
110 @decorators.attr(type='smoke')
111 def test_show_action_plan_with_links(self):
112 _, action_plan = self.client.show_action_plan(
113 self.action_plan['uuid'])
114 self.assertIn('links', action_plan.keys())
115 self.assertEqual(2, len(action_plan['links']))
116 self.assertIn(action_plan['uuid'],
117 action_plan['links'][0]['href'])
118
119 @decorators.attr(type="smoke")
120 def test_list_action_plans(self):
121 _, body = self.client.list_action_plans()
122 self.assertIn(self.action_plan['uuid'],
123 [i['uuid'] for i in body['action_plans']])
124 # Verify self links.
125 for action_plan in body['action_plans']:
126 self.validate_self_link('action_plans', action_plan['uuid'],
127 action_plan['links'][0]['href'])
128
129 @decorators.attr(type='smoke')
130 def test_list_with_limit(self):
131 # We create 3 extra audits to exceed the limit we fix
132 for _ in range(3):
133 self.create_action_plan(self.audit_template['uuid'])
134
135 _, body = self.client.list_action_plans(limit=3)
136
137 next_marker = body['action_plans'][-1]['uuid']
138
139 self.assertEqual(3, len(body['action_plans']))
140 self.assertIn(next_marker, body['next'])
diff --git a/watcher_tempest_plugin/tests/api/admin/test_api_discovery.py b/watcher_tempest_plugin/tests/api/admin/test_api_discovery.py
deleted file mode 100644
index f30cb4b..0000000
--- a/watcher_tempest_plugin/tests/api/admin/test_api_discovery.py
+++ /dev/null
@@ -1,47 +0,0 @@
1# -*- encoding: utf-8 -*-
2# Copyright (c) 2016 b<>com
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13# implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17from tempest.lib import decorators
18
19from watcher_tempest_plugin.tests.api.admin import base
20
21
22class TestApiDiscovery(base.BaseInfraOptimTest):
23 """Tests for API discovery features."""
24
25 @decorators.attr(type='smoke')
26 def test_api_versions(self):
27 _, descr = self.client.get_api_description()
28 expected_versions = ('v1',)
29 versions = [version['id'] for version in descr['versions']]
30
31 for v in expected_versions:
32 self.assertIn(v, versions)
33
34 @decorators.attr(type='smoke')
35 def test_default_version(self):
36 _, descr = self.client.get_api_description()
37 default_version = descr['default_version']
38 self.assertEqual('v1', default_version['id'])
39
40 @decorators.attr(type='smoke')
41 def test_version_1_resources(self):
42 _, descr = self.client.get_version_description(version='v1')
43 expected_resources = ('audit_templates', 'audits', 'action_plans',
44 'actions', 'links', 'media_types')
45
46 for res in expected_resources:
47 self.assertIn(res, descr)
diff --git a/watcher_tempest_plugin/tests/api/admin/test_audit.py b/watcher_tempest_plugin/tests/api/admin/test_audit.py
deleted file mode 100644
index 13a187e..0000000
--- a/watcher_tempest_plugin/tests/api/admin/test_audit.py
+++ /dev/null
@@ -1,221 +0,0 @@
1# -*- encoding: utf-8 -*-
2# Copyright (c) 2016 b<>com
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13# implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17from __future__ import unicode_literals
18
19import functools
20
21from tempest.lib.common.utils import test_utils
22from tempest.lib import decorators
23from tempest.lib import exceptions
24
25from watcher_tempest_plugin.tests.api.admin import base
26
27
28class TestCreateUpdateDeleteAudit(base.BaseInfraOptimTest):
29 """Tests for audit."""
30
31 audit_states = ['ONGOING', 'SUCCEEDED', 'FAILED',
32 'CANCELLED', 'DELETED', 'PENDING', 'SUSPENDED']
33
34 def assert_expected(self, expected, actual,
35 keys=('created_at', 'updated_at',
36 'deleted_at', 'state')):
37 super(TestCreateUpdateDeleteAudit, self).assert_expected(
38 expected, actual, keys)
39
40 @decorators.attr(type='smoke')
41 def test_create_audit_oneshot(self):
42 _, goal = self.client.show_goal("dummy")
43 _, audit_template = self.create_audit_template(goal['uuid'])
44
45 audit_params = dict(
46 audit_template_uuid=audit_template['uuid'],
47 audit_type='ONESHOT',
48 )
49
50 _, body = self.create_audit(**audit_params)
51 audit_params.pop('audit_template_uuid')
52 audit_params['goal_uuid'] = goal['uuid']
53 self.assert_expected(audit_params, body)
54
55 _, audit = self.client.show_audit(body['uuid'])
56 self.assert_expected(audit, body)
57
58 @decorators.attr(type='smoke')
59 def test_create_audit_continuous(self):
60 _, goal = self.client.show_goal("dummy")
61 _, audit_template = self.create_audit_template(goal['uuid'])
62
63 audit_params = dict(
64 audit_template_uuid=audit_template['uuid'],
65 audit_type='CONTINUOUS',
66 interval='7200',
67 )
68
69 _, body = self.create_audit(**audit_params)
70 audit_params.pop('audit_template_uuid')
71 audit_params['goal_uuid'] = goal['uuid']
72 self.assert_expected(audit_params, body)
73
74 _, audit = self.client.show_audit(body['uuid'])
75 self.assert_expected(audit, body)
76
77 @decorators.attr(type='smoke')
78 def test_create_audit_with_wrong_audit_template(self):
79 audit_params = dict(
80 audit_template_uuid='INVALID',
81 audit_type='ONESHOT',
82 )
83
84 self.assertRaises(
85 exceptions.BadRequest, self.create_audit, **audit_params)
86
87 @decorators.attr(type='smoke')
88 def test_create_audit_with_invalid_state(self):
89 _, goal = self.client.show_goal("dummy")
90 _, audit_template = self.create_audit_template(goal['uuid'])
91
92 audit_params = dict(
93 audit_template_uuid=audit_template['uuid'],
94 state='INVALID',
95 )
96
97 self.assertRaises(
98 exceptions.BadRequest, self.create_audit, **audit_params)
99
100 @decorators.attr(type='smoke')
101 def test_create_audit_with_no_state(self):
102 _, goal = self.client.show_goal("dummy")
103 _, audit_template = self.create_audit_template(goal['uuid'])
104
105 audit_params = dict(
106 audit_template_uuid=audit_template['uuid'],
107 state='',
108 )
109
110 _, body = self.create_audit(**audit_params)
111 audit_params.pop('audit_template_uuid')
112 audit_params['goal_uuid'] = goal['uuid']
113 self.assert_expected(audit_params, body)
114
115 _, audit = self.client.show_audit(body['uuid'])
116
117 initial_audit_state = audit.pop('state')
118 self.assertIn(initial_audit_state, self.audit_states)
119
120 self.assert_expected(audit, body)
121
122 @decorators.attr(type='smoke')
123 def test_delete_audit(self):
124 _, goal = self.client.show_goal("dummy")
125 _, audit_template = self.create_audit_template(goal['uuid'])
126 _, body = self.create_audit(audit_template['uuid'])
127 audit_uuid = body['uuid']
128
129 test_utils.call_until_true(
130 func=functools.partial(
131 self.is_audit_idle, audit_uuid),
132 duration=10,
133 sleep_for=.5
134 )
135
136 def is_audit_deleted(uuid):
137 try:
138 return not bool(self.client.show_audit(uuid))
139 except exceptions.NotFound:
140 return True
141
142 self.delete_audit(audit_uuid)
143
144 test_utils.call_until_true(
145 func=functools.partial(is_audit_deleted, audit_uuid),
146 duration=5,
147 sleep_for=1
148 )
149
150 self.assertTrue(is_audit_deleted(audit_uuid))
151
152
153class TestShowListAudit(base.BaseInfraOptimTest):
154 """Tests for audit."""
155
156 audit_states = ['ONGOING', 'SUCCEEDED', 'FAILED',
157 'CANCELLED', 'DELETED', 'PENDING', 'SUSPENDED']
158
159 @classmethod
160 def resource_setup(cls):
161 super(TestShowListAudit, cls).resource_setup()
162 _, cls.goal = cls.client.show_goal("dummy")
163 _, cls.audit_template = cls.create_audit_template(cls.goal['uuid'])
164 _, cls.audit = cls.create_audit(cls.audit_template['uuid'])
165
166 def assert_expected(self, expected, actual,
167 keys=('created_at', 'updated_at',
168 'deleted_at', 'state')):
169 super(TestShowListAudit, self).assert_expected(
170 expected, actual, keys)
171
172 @decorators.attr(type='smoke')
173 def test_show_audit(self):
174 _, audit = self.client.show_audit(
175 self.audit['uuid'])
176
177 initial_audit = self.audit.copy()
178 del initial_audit['state']
179 audit_state = audit['state']
180 actual_audit = audit.copy()
181 del actual_audit['state']
182
183 self.assertIn(audit_state, self.audit_states)
184 self.assert_expected(initial_audit, actual_audit)
185
186 @decorators.attr(type='smoke')
187 def test_show_audit_with_links(self):
188 _, audit = self.client.show_audit(
189 self.audit['uuid'])
190 self.assertIn('links', audit.keys())
191 self.assertEqual(2, len(audit['links']))
192 self.assertIn(audit['uuid'],
193 audit['links'][0]['href'])
194
195 @decorators.attr(type="smoke")
196 def test_list_audits(self):
197 _, body = self.client.list_audits()
198 self.assertIn(self.audit['uuid'],
199 [i['uuid'] for i in body['audits']])
200 # Verify self links.
201 for audit in body['audits']:
202 self.validate_self_link('audits', audit['uuid'],
203 audit['links'][0]['href'])
204
205 @decorators.attr(type='smoke')
206 def test_list_with_limit(self):
207 # We create 3 extra audits to exceed the limit we fix
208 for _ in range(3):
209 self.create_audit(self.audit_template['uuid'])
210
211 _, body = self.client.list_audits(limit=3)
212
213 next_marker = body['audits'][-1]['uuid']
214 self.assertEqual(3, len(body['audits']))
215 self.assertIn(next_marker, body['next'])
216
217 @decorators.attr(type='smoke')
218 def test_list_audits_related_to_given_audit_template(self):
219 _, body = self.client.list_audits(
220 goal=self.goal['uuid'])
221 self.assertIn(self.audit['uuid'], [n['uuid'] for n in body['audits']])
diff --git a/watcher_tempest_plugin/tests/api/admin/test_audit_template.py b/watcher_tempest_plugin/tests/api/admin/test_audit_template.py
deleted file mode 100644
index 75ac80a..0000000
--- a/watcher_tempest_plugin/tests/api/admin/test_audit_template.py
+++ /dev/null
@@ -1,226 +0,0 @@
1# -*- encoding: utf-8 -*-
2# Copyright (c) 2016 b<>com
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13# implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17from __future__ import unicode_literals
18
19from oslo_utils import uuidutils
20
21from tempest.lib import decorators
22from tempest.lib import exceptions
23
24from watcher_tempest_plugin.tests.api.admin import base
25
26
27class TestCreateDeleteAuditTemplate(base.BaseInfraOptimTest):
28 """Tests on audit templates"""
29
30 @decorators.attr(type='smoke')
31 def test_create_audit_template(self):
32 goal_name = "dummy"
33 _, goal = self.client.show_goal(goal_name)
34
35 params = {
36 'name': 'my at name %s' % uuidutils.generate_uuid(),
37 'description': 'my at description',
38 'goal': goal['uuid']}
39 expected_data = {
40 'name': params['name'],
41 'description': params['description'],
42 'goal_uuid': params['goal'],
43 'goal_name': goal_name,
44 'strategy_uuid': None,
45 'strategy_name': None}
46
47 _, body = self.create_audit_template(**params)
48 self.assert_expected(expected_data, body)
49
50 _, audit_template = self.client.show_audit_template(body['uuid'])
51 self.assert_expected(audit_template, body)
52
53 @decorators.attr(type='smoke')
54 def test_create_audit_template_unicode_description(self):
55 goal_name = "dummy"
56 _, goal = self.client.show_goal(goal_name)
57 # Use a unicode string for testing:
58 params = {
59 'name': 'my at name %s' % uuidutils.generate_uuid(),
60 'description': 'my àt déscrïptïôn',
61 'goal': goal['uuid']}
62
63 expected_data = {
64 'name': params['name'],
65 'description': params['description'],
66 'goal_uuid': params['goal'],
67 'goal_name': goal_name,
68 'strategy_uuid': None,
69 'strategy_name': None}
70
71 _, body = self.create_audit_template(**params)
72 self.assert_expected(expected_data, body)
73
74 _, audit_template = self.client.show_audit_template(body['uuid'])
75 self.assert_expected(audit_template, body)
76
77 @decorators.attr(type='smoke')
78 def test_delete_audit_template(self):
79 _, goal = self.client.show_goal("dummy")
80 _, body = self.create_audit_template(goal=goal['uuid'])
81 audit_uuid = body['uuid']
82
83 self.delete_audit_template(audit_uuid)
84
85 self.assertRaises(exceptions.NotFound, self.client.show_audit_template,
86 audit_uuid)
87
88
89class TestAuditTemplate(base.BaseInfraOptimTest):
90 """Tests for audit_template."""
91
92 @classmethod
93 def resource_setup(cls):
94 super(TestAuditTemplate, cls).resource_setup()
95 _, cls.goal = cls.client.show_goal("dummy")
96 _, cls.strategy = cls.client.show_strategy("dummy")
97 _, cls.audit_template = cls.create_audit_template(
98 goal=cls.goal['uuid'], strategy=cls.strategy['uuid'])
99
100 @decorators.attr(type='smoke')
101 def test_show_audit_template(self):
102 _, audit_template = self.client.show_audit_template(
103 self.audit_template['uuid'])
104
105 self.assert_expected(self.audit_template, audit_template)
106
107 @decorators.attr(type='smoke')
108 def test_filter_audit_template_by_goal_uuid(self):
109 _, audit_templates = self.client.list_audit_templates(
110 goal=self.audit_template['goal_uuid'])
111
112 audit_template_uuids = [
113 at["uuid"] for at in audit_templates['audit_templates']]
114 self.assertIn(self.audit_template['uuid'], audit_template_uuids)
115
116 @decorators.attr(type='smoke')
117 def test_filter_audit_template_by_strategy_uuid(self):
118 _, audit_templates = self.client.list_audit_templates(
119 strategy=self.audit_template['strategy_uuid'])
120
121 audit_template_uuids = [
122 at["uuid"] for at in audit_templates['audit_templates']]
123 self.assertIn(self.audit_template['uuid'], audit_template_uuids)
124
125 @decorators.attr(type='smoke')
126 def test_show_audit_template_with_links(self):
127 _, audit_template = self.client.show_audit_template(
128 self.audit_template['uuid'])
129 self.assertIn('links', audit_template.keys())
130 self.assertEqual(2, len(audit_template['links']))
131 self.assertIn(audit_template['uuid'],
132 audit_template['links'][0]['href'])
133
134 @decorators.attr(type="smoke")
135 def test_list_audit_templates(self):
136 _, body = self.client.list_audit_templates()
137 self.assertIn(self.audit_template['uuid'],
138 [i['uuid'] for i in body['audit_templates']])
139 # Verify self links.
140 for audit_template in body['audit_templates']:
141 self.validate_self_link('audit_templates', audit_template['uuid'],
142 audit_template['links'][0]['href'])
143
144 @decorators.attr(type='smoke')
145 def test_list_with_limit(self):
146 # We create 3 extra audit templates to exceed the limit we fix
147 for _ in range(3):
148 self.create_audit_template(self.goal['uuid'])
149
150 _, body = self.client.list_audit_templates(limit=3)
151
152 next_marker = body['audit_templates'][-1]['uuid']
153 self.assertEqual(3, len(body['audit_templates']))
154 self.assertIn(next_marker, body['next'])
155
156 @decorators.attr(type='smoke')
157 def test_update_audit_template_replace(self):
158 _, new_goal = self.client.show_goal("server_consolidation")
159 _, new_strategy = self.client.show_strategy("basic")
160
161 params = {'name': 'my at name %s' % uuidutils.generate_uuid(),
162 'description': 'my at description',
163 'goal': self.goal['uuid']}
164
165 _, body = self.create_audit_template(**params)
166
167 new_name = 'my at new name %s' % uuidutils.generate_uuid()
168 new_description = 'my new at description'
169
170 patch = [{'path': '/name',
171 'op': 'replace',
172 'value': new_name},
173 {'path': '/description',
174 'op': 'replace',
175 'value': new_description},
176 {'path': '/goal',
177 'op': 'replace',
178 'value': new_goal['uuid']},
179 {'path': '/strategy',
180 'op': 'replace',
181 'value': new_strategy['uuid']}]
182
183 self.client.update_audit_template(body['uuid'], patch)
184
185 _, body = self.client.show_audit_template(body['uuid'])
186 self.assertEqual(new_name, body['name'])
187 self.assertEqual(new_description, body['description'])
188 self.assertEqual(new_goal['uuid'], body['goal_uuid'])
189 self.assertEqual(new_strategy['uuid'], body['strategy_uuid'])
190
191 @decorators.attr(type='smoke')
192 def test_update_audit_template_remove(self):
193 description = 'my at description'
194 name = 'my at name %s' % uuidutils.generate_uuid()
195 params = {'name': name,
196 'description': description,
197 'goal': self.goal['uuid']}
198
199 _, audit_template = self.create_audit_template(**params)
200
201 # Removing the description
202 self.client.update_audit_template(
203 audit_template['uuid'],
204 [{'path': '/description', 'op': 'remove'}])
205
206 _, body = self.client.show_audit_template(audit_template['uuid'])
207 self.assertIsNone(body.get('description'))
208
209 # Assert nothing else was changed
210 self.assertEqual(name, body['name'])
211 self.assertIsNone(body['description'])
212 self.assertEqual(self.goal['uuid'], body['goal_uuid'])
213
214 @decorators.attr(type='smoke')
215 def test_update_audit_template_add(self):
216 params = {'name': 'my at name %s' % uuidutils.generate_uuid(),
217 'goal': self.goal['uuid']}
218
219 _, body = self.create_audit_template(**params)
220
221 patch = [{'path': '/description', 'op': 'add', 'value': 'description'}]
222
223 self.client.update_audit_template(body['uuid'], patch)
224
225 _, body = self.client.show_audit_template(body['uuid'])
226 self.assertEqual('description', body['description'])
diff --git a/watcher_tempest_plugin/tests/api/admin/test_goal.py b/watcher_tempest_plugin/tests/api/admin/test_goal.py
deleted file mode 100644
index 2cf228e..0000000
--- a/watcher_tempest_plugin/tests/api/admin/test_goal.py
+++ /dev/null
@@ -1,66 +0,0 @@
1# -*- encoding: utf-8 -*-
2# Copyright (c) 2016 b<>com
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13# implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17from __future__ import unicode_literals
18
19from tempest.lib import decorators
20
21from watcher_tempest_plugin.tests.api.admin import base
22
23
24class TestShowListGoal(base.BaseInfraOptimTest):
25 """Tests for goals"""
26
27 DUMMY_GOAL = "dummy"
28
29 @classmethod
30 def resource_setup(cls):
31 super(TestShowListGoal, cls).resource_setup()
32
33 def assert_expected(self, expected, actual,
34 keys=('created_at', 'updated_at', 'deleted_at')):
35 super(TestShowListGoal, self).assert_expected(
36 expected, actual, keys)
37
38 @decorators.attr(type='smoke')
39 def test_show_goal(self):
40 _, goal = self.client.show_goal(self.DUMMY_GOAL)
41
42 self.assertEqual(self.DUMMY_GOAL, goal['name'])
43 expected_fields = {
44 'created_at', 'deleted_at', 'display_name',
45 'efficacy_specification', 'links', 'name',
46 'updated_at', 'uuid'}
47 self.assertEqual(expected_fields, set(goal.keys()))
48
49 @decorators.attr(type='smoke')
50 def test_show_goal_with_links(self):
51 _, goal = self.client.show_goal(self.DUMMY_GOAL)
52 self.assertIn('links', goal.keys())
53 self.assertEqual(2, len(goal['links']))
54 self.assertIn(goal['uuid'],
55 goal['links'][0]['href'])
56
57 @decorators.attr(type="smoke")
58 def test_list_goals(self):
59 _, body = self.client.list_goals()
60 self.assertIn(self.DUMMY_GOAL,
61 [i['name'] for i in body['goals']])
62
63 # Verify self links.
64 for goal in body['goals']:
65 self.validate_self_link('goals', goal['uuid'],
66 goal['links'][0]['href'])
diff --git a/watcher_tempest_plugin/tests/api/admin/test_scoring_engine.py b/watcher_tempest_plugin/tests/api/admin/test_scoring_engine.py
deleted file mode 100644
index 466fe41..0000000
--- a/watcher_tempest_plugin/tests/api/admin/test_scoring_engine.py
+++ /dev/null
@@ -1,65 +0,0 @@
1# Copyright (c) 2016 b<>com
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12# implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16from __future__ import unicode_literals
17
18from tempest.lib import decorators
19
20from watcher_tempest_plugin.tests.api.admin import base
21
22
23class TestShowListScoringEngine(base.BaseInfraOptimTest):
24 """Tests for scoring engines"""
25
26 DUMMY_SCORING_ENGINE = "dummy_scorer"
27
28 @classmethod
29 def resource_setup(cls):
30 super(TestShowListScoringEngine, cls).resource_setup()
31
32 def assert_expected(self, expected, actual,
33 keys=('created_at', 'updated_at', 'deleted_at')):
34 super(TestShowListScoringEngine, self).assert_expected(
35 expected, actual, keys)
36
37 @decorators.attr(type='smoke')
38 def test_show_scoring_engine(self):
39 _, scoring_engine = self.client.show_scoring_engine(
40 self.DUMMY_SCORING_ENGINE)
41
42 self.assertEqual(self.DUMMY_SCORING_ENGINE, scoring_engine['name'])
43
44 expected_fields = {'metainfo', 'description', 'name', 'uuid', 'links'}
45 self.assertEqual(expected_fields, set(scoring_engine.keys()))
46
47 @decorators.attr(type='smoke')
48 def test_show_scoring_engine_with_links(self):
49 _, scoring_engine = self.client.show_scoring_engine(
50 self.DUMMY_SCORING_ENGINE)
51 self.assertIn('links', scoring_engine.keys())
52 self.assertEqual(2, len(scoring_engine['links']))
53 self.assertIn(scoring_engine['uuid'],
54 scoring_engine['links'][0]['href'])
55
56 @decorators.attr(type="smoke")
57 def test_list_scoring_engines(self):
58 _, body = self.client.list_scoring_engines()
59 self.assertIn(self.DUMMY_SCORING_ENGINE,
60 [i['name'] for i in body['scoring_engines']])
61
62 # Verify self links.
63 for scoring_engine in body['scoring_engines']:
64 self.validate_self_link('scoring_engines', scoring_engine['uuid'],
65 scoring_engine['links'][0]['href'])
diff --git a/watcher_tempest_plugin/tests/api/admin/test_service.py b/watcher_tempest_plugin/tests/api/admin/test_service.py
deleted file mode 100644
index 948d8b1..0000000
--- a/watcher_tempest_plugin/tests/api/admin/test_service.py
+++ /dev/null
@@ -1,90 +0,0 @@
1# -*- encoding: utf-8 -*-
2# Copyright (c) 2016 Servionica
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13# implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17from __future__ import unicode_literals
18
19from tempest.lib import decorators
20
21from watcher_tempest_plugin.tests.api.admin import base
22
23
24class TestShowListService(base.BaseInfraOptimTest):
25 """Tests for services"""
26
27 DECISION_ENGINE = "watcher-decision-engine"
28 APPLIER = "watcher-applier"
29
30 @classmethod
31 def resource_setup(cls):
32 super(TestShowListService, cls).resource_setup()
33
34 def assert_expected(self, expected, actual,
35 keys=('created_at', 'updated_at', 'deleted_at')):
36 super(TestShowListService, self).assert_expected(
37 expected, actual, keys)
38
39 @decorators.attr(type='smoke')
40 def test_show_service(self):
41 _, body = self.client.list_services()
42 self.assertIn('services', body)
43 services = body['services']
44 self.assertIn(self.DECISION_ENGINE,
45 [i['name'] for i in body['services']])
46
47 service_id = filter(lambda x: self.DECISION_ENGINE == x['name'],
48 services)[0]['id']
49 _, service = self.client.show_service(service_id)
50
51 self.assertEqual(self.DECISION_ENGINE, service['name'])
52 self.assertIn("host", service.keys())
53 self.assertIn("last_seen_up", service.keys())
54 self.assertIn("status", service.keys())
55
56 @decorators.attr(type='smoke')
57 def test_show_service_with_links(self):
58 _, body = self.client.list_services()
59 self.assertIn('services', body)
60 services = body['services']
61 self.assertIn(self.DECISION_ENGINE,
62 [i['name'] for i in body['services']])
63
64 service_id = filter(lambda x: self.DECISION_ENGINE == x['name'],
65 services)[0]['id']
66 _, service = self.client.show_service(service_id)
67
68 self.assertIn('links', service.keys())
69 self.assertEqual(2, len(service['links']))
70 self.assertIn(str(service['id']),
71 service['links'][0]['href'])
72
73 @decorators.attr(type="smoke")
74 def test_list_services(self):
75 _, body = self.client.list_services()
76 self.assertIn('services', body)
77 services = body['services']
78 self.assertIn(self.DECISION_ENGINE,
79 [i['name'] for i in body['services']])
80
81 for service in services:
82 self.assertTrue(
83 all(val is not None for key, val in service.items()
84 if key in ['id', 'name', 'host', 'status',
85 'last_seen_up']))
86
87 # Verify self links.
88 for service in body['services']:
89 self.validate_self_link('services', service['id'],
90 service['links'][0]['href'])
diff --git a/watcher_tempest_plugin/tests/api/admin/test_strategy.py b/watcher_tempest_plugin/tests/api/admin/test_strategy.py
deleted file mode 100644
index 73eefd7..0000000
--- a/watcher_tempest_plugin/tests/api/admin/test_strategy.py
+++ /dev/null
@@ -1,69 +0,0 @@
1# -*- encoding: utf-8 -*-
2# Copyright (c) 2016 b<>com
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13# implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17from __future__ import unicode_literals
18
19from tempest.lib import decorators
20
21from watcher_tempest_plugin.tests.api.admin import base
22
23
24class TestShowListStrategy(base.BaseInfraOptimTest):
25 """Tests for strategies"""
26
27 DUMMY_STRATEGY = "dummy"
28
29 @classmethod
30 def resource_setup(cls):
31 super(TestShowListStrategy, cls).resource_setup()
32
33 def assert_expected(self, expected, actual,
34 keys=('created_at', 'updated_at', 'deleted_at')):
35 super(TestShowListStrategy, self).assert_expected(
36 expected, actual, keys)
37
38 @decorators.attr(type='smoke')
39 def test_show_strategy(self):
40 _, strategy = self.client.show_strategy(self.DUMMY_STRATEGY)
41
42 self.assertEqual(self.DUMMY_STRATEGY, strategy['name'])
43 self.assertIn("display_name", strategy.keys())
44
45 @decorators.attr(type='smoke')
46 def test_show_strategy_with_links(self):
47 _, strategy = self.client.show_strategy(self.DUMMY_STRATEGY)
48 self.assertIn('links', strategy.keys())
49 self.assertEqual(2, len(strategy['links']))
50 self.assertIn(strategy['uuid'],
51 strategy['links'][0]['href'])
52
53 @decorators.attr(type="smoke")
54 def test_list_strategies(self):
55 _, body = self.client.list_strategies()
56 self.assertIn('strategies', body)
57 strategies = body['strategies']
58 self.assertIn(self.DUMMY_STRATEGY,
59 [i['name'] for i in body['strategies']])
60
61 for strategy in strategies:
62 self.assertTrue(
63 all(val is not None for key, val in strategy.items()
64 if key in ['uuid', 'name', 'display_name', 'goal_uuid']))
65
66 # Verify self links.
67 for strategy in body['strategies']:
68 self.validate_self_link('strategies', strategy['uuid'],
69 strategy['links'][0]['href'])
diff --git a/watcher_tempest_plugin/tests/scenario/__init__.py b/watcher_tempest_plugin/tests/scenario/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/watcher_tempest_plugin/tests/scenario/__init__.py
+++ /dev/null
diff --git a/watcher_tempest_plugin/tests/scenario/base.py b/watcher_tempest_plugin/tests/scenario/base.py
deleted file mode 100644
index 18688b0..0000000
--- a/watcher_tempest_plugin/tests/scenario/base.py
+++ /dev/null
@@ -1,185 +0,0 @@
1# -*- encoding: utf-8 -*-
2# Copyright (c) 2016 b<>com
3#
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14# implied.
15# See the License for the specific language governing permissions and
16# limitations under the License.
17#
18
19from __future__ import unicode_literals
20
21import time
22
23from oslo_log import log
24from tempest import config
25from tempest import exceptions
26from tempest.lib.common.utils import data_utils
27from tempest.lib.common.utils import test_utils
28
29from watcher_tempest_plugin import infra_optim_clients as clients
30from watcher_tempest_plugin.tests.scenario import manager
31
32LOG = log.getLogger(__name__)
33CONF = config.CONF
34
35
36class BaseInfraOptimScenarioTest(manager.ScenarioTest):
37 """Base class for Infrastructure Optimization API tests."""
38
39 # States where the object is waiting for some event to perform a transition
40 IDLE_STATES = ('RECOMMENDED', 'FAILED', 'SUCCEEDED', 'CANCELLED')
41 # States where the object can only be DELETED (end of its life-cycle)
42 FINISHED_STATES = ('FAILED', 'SUCCEEDED', 'CANCELLED', 'SUPERSEDED')
43
44 @classmethod
45 def setup_credentials(cls):
46 cls._check_network_config()
47 super(BaseInfraOptimScenarioTest, cls).setup_credentials()
48 cls.mgr = clients.AdminManager()
49
50 @classmethod
51 def setup_clients(cls):
52 super(BaseInfraOptimScenarioTest, cls).setup_clients()
53 cls.client = cls.mgr.io_client
54
55 @classmethod
56 def resource_setup(cls):
57 super(BaseInfraOptimScenarioTest, cls).resource_setup()
58
59 @classmethod
60 def resource_cleanup(cls):
61 """Ensure that all created objects get destroyed."""
62 super(BaseInfraOptimScenarioTest, cls).resource_cleanup()
63
64 @classmethod
65 def wait_for(cls, condition, timeout=30):
66 start_time = time.time()
67 while time.time() - start_time < timeout:
68 if condition():
69 break
70 time.sleep(.5)
71
72 @classmethod
73 def _check_network_config(cls):
74 if not CONF.network.public_network_id:
75 msg = 'public network not defined.'
76 LOG.error(msg)
77 raise exceptions.InvalidConfiguration(msg)
78
79 @classmethod
80 def _are_all_action_plans_finished(cls):
81 _, action_plans = cls.client.list_action_plans()
82 return all([ap['state'] in cls.FINISHED_STATES
83 for ap in action_plans['action_plans']])
84
85 def wait_for_all_action_plans_to_finish(self):
86 assert test_utils.call_until_true(
87 func=self._are_all_action_plans_finished,
88 duration=300,
89 sleep_for=5
90 )
91
92 # ### AUDIT TEMPLATES ### #
93
94 def create_audit_template(self, goal, name=None, description=None,
95 strategy=None):
96 """Wrapper utility for creating a test audit template
97
98 :param goal: Goal UUID or name related to the audit template.
99 :param name: The name of the audit template. Default: My Audit Template
100 :param description: The description of the audit template.
101 :param strategy: Strategy UUID or name related to the audit template.
102 :return: A tuple with The HTTP response and its body
103 """
104 description = description or data_utils.rand_name(
105 'test-audit_template')
106 resp, body = self.client.create_audit_template(
107 name=name, description=description, goal=goal, strategy=strategy)
108
109 self.addCleanup(
110 self.delete_audit_template,
111 audit_template_uuid=body["uuid"]
112 )
113
114 return resp, body
115
116 def delete_audit_template(self, audit_template_uuid):
117 """Deletes a audit_template having the specified UUID
118
119 :param audit_template_uuid: The unique identifier of the audit template
120 :return: Server response
121 """
122 resp, _ = self.client.delete_audit_template(audit_template_uuid)
123 return resp
124
125 # ### AUDITS ### #
126
127 def create_audit(self, audit_template_uuid, audit_type='ONESHOT',
128 state=None, interval=None, parameters=None):
129 """Wrapper utility for creating a test audit
130
131 :param audit_template_uuid: Audit Template UUID this audit will use
132 :param type: Audit type (either ONESHOT or CONTINUOUS)
133 :param state: Audit state (str)
134 :param interval: Audit interval in seconds (int)
135 :param parameters: list of execution parameters
136 :return: A tuple with The HTTP response and its body
137 """
138 resp, body = self.client.create_audit(
139 audit_template_uuid=audit_template_uuid, audit_type=audit_type,
140 state=state, interval=interval, parameters=parameters)
141
142 self.addCleanup(self.delete_audit, audit_uuid=body["uuid"])
143 return resp, body
144
145 def delete_audit(self, audit_uuid):
146 """Deletes an audit having the specified UUID
147
148 :param audit_uuid: The unique identifier of the audit.
149 :return: the HTTP response
150 """
151
152 _, action_plans = self.client.list_action_plans(audit_uuid=audit_uuid)
153 for action_plan in action_plans.get("action_plans", []):
154 self.delete_action_plan(action_plan_uuid=action_plan["uuid"])
155
156 resp, _ = self.client.delete_audit(audit_uuid)
157 return resp
158
159 def has_audit_succeeded(self, audit_uuid):
160 _, audit = self.client.show_audit(audit_uuid)
161 if audit.get('state') in ('FAILED', 'CANCELLED'):
162 raise ValueError()
163
164 return audit.get('state') == 'SUCCEEDED'
165
166 @classmethod
167 def has_audit_finished(cls, audit_uuid):
168 _, audit = cls.client.show_audit(audit_uuid)
169 return audit.get('state') in cls.FINISHED_STATES
170
171 # ### ACTION PLANS ### #
172
173 def delete_action_plan(self, action_plan_uuid):
174 """Deletes an action plan having the specified UUID
175
176 :param action_plan_uuid: The unique identifier of the action plan.
177 :return: the HTTP response
178 """
179 resp, _ = self.client.delete_action_plan(action_plan_uuid)
180 return resp
181
182 def has_action_plan_finished(self, action_plan_uuid):
183 _, action_plan = self.client.show_action_plan(action_plan_uuid)
184 return action_plan.get('state') in ('FAILED', 'SUCCEEDED', 'CANCELLED',
185 'SUPERSEDED')
diff --git a/watcher_tempest_plugin/tests/scenario/manager.py b/watcher_tempest_plugin/tests/scenario/manager.py
deleted file mode 100644
index 5364525..0000000
--- a/watcher_tempest_plugin/tests/scenario/manager.py
+++ /dev/null
@@ -1,206 +0,0 @@
1# Copyright 2012 OpenStack Foundation
2# Copyright 2013 IBM Corp.
3# All Rights Reserved.
4#
5# Licensed under the Apache License, Version 2.0 (the "License"); you may
6# not use this file except in compliance with the License. You may obtain
7# a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14# License for the specific language governing permissions and limitations
15# under the License.
16
17from oslo_log import log
18
19from tempest.common import compute
20from tempest.common import waiters
21from tempest import config
22from tempest.lib.common.utils import data_utils
23from tempest.lib.common.utils import test_utils
24from tempest.lib import exceptions as lib_exc
25import tempest.test
26
27CONF = config.CONF
28
29LOG = log.getLogger(__name__)
30
31
32class ScenarioTest(tempest.test.BaseTestCase):
33 """Base class for scenario tests. Uses tempest own clients. """
34
35 credentials = ['primary']
36
37 @classmethod
38 def setup_clients(cls):
39 super(ScenarioTest, cls).setup_clients()
40 # Clients (in alphabetical order)
41 cls.flavors_client = cls.os_primary.flavors_client
42 cls.compute_floating_ips_client = (
43 cls.os_primary.compute_floating_ips_client)
44 if CONF.service_available.glance:
45 # Check if glance v1 is available to determine which client to use.
46 if CONF.image_feature_enabled.api_v1:
47 cls.image_client = cls.os_primary.image_client
48 elif CONF.image_feature_enabled.api_v2:
49 cls.image_client = cls.os_primary.image_client_v2
50 else:
51 raise lib_exc.InvalidConfiguration(
52 'Either api_v1 or api_v2 must be True in '
53 '[image-feature-enabled].')
54 # Compute image client
55 cls.compute_images_client = cls.os_primary.compute_images_client
56 cls.keypairs_client = cls.os_primary.keypairs_client
57 # Nova security groups client
58 cls.compute_security_groups_client = (
59 cls.os_primary.compute_security_groups_client)
60 cls.compute_security_group_rules_client = (
61 cls.os_primary.compute_security_group_rules_client)
62 cls.servers_client = cls.os_primary.servers_client
63 cls.interface_client = cls.os_primary.interfaces_client
64 # Neutron network client
65 cls.networks_client = cls.os_primary.networks_client
66 cls.ports_client = cls.os_primary.ports_client
67 cls.routers_client = cls.os_primary.routers_client
68 cls.subnets_client = cls.os_primary.subnets_client
69 cls.floating_ips_client = cls.os_primary.floating_ips_client
70 cls.security_groups_client = cls.os_primary.security_groups_client
71 cls.security_group_rules_client = (
72 cls.os_primary.security_group_rules_client)
73
74 if CONF.volume_feature_enabled.api_v2:
75 cls.volumes_client = cls.os_primary.volumes_v2_client
76 cls.snapshots_client = cls.os_primary.snapshots_v2_client
77 else:
78 cls.volumes_client = cls.os_primary.volumes_client
79 cls.snapshots_client = cls.os_primary.snapshots_client
80
81 # ## Test functions library
82 #
83 # The create_[resource] functions only return body and discard the
84 # resp part which is not used in scenario tests
85
86 def _create_port(self, network_id, client=None, namestart='port-quotatest',
87 **kwargs):
88 if not client:
89 client = self.ports_client
90 name = data_utils.rand_name(namestart)
91 result = client.create_port(
92 name=name,
93 network_id=network_id,
94 **kwargs)
95 self.assertIsNotNone(result, 'Unable to allocate port')
96 port = result['port']
97 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
98 client.delete_port, port['id'])
99 return port
100
101 def create_keypair(self, client=None):
102 if not client:
103 client = self.keypairs_client
104 name = data_utils.rand_name(self.__class__.__name__)
105 # We don't need to create a keypair by pubkey in scenario
106 body = client.create_keypair(name=name)
107 self.addCleanup(client.delete_keypair, name)
108 return body['keypair']
109
110 def create_server(self, name=None, image_id=None, flavor=None,
111 validatable=False, wait_until='ACTIVE',
112 clients=None, **kwargs):
113 """Wrapper utility that returns a test server.
114
115 This wrapper utility calls the common create test server and
116 returns a test server. The purpose of this wrapper is to minimize
117 the impact on the code of the tests already using this
118 function.
119 """
120
121 # NOTE(jlanoux): As a first step, ssh checks in the scenario
122 # tests need to be run regardless of the run_validation and
123 # validatable parameters and thus until the ssh validation job
124 # becomes voting in CI. The test resources management and IP
125 # association are taken care of in the scenario tests.
126 # Therefore, the validatable parameter is set to false in all
127 # those tests. In this way create_server just return a standard
128 # server and the scenario tests always perform ssh checks.
129
130 # Needed for the cross_tenant_traffic test:
131 if clients is None:
132 clients = self.os_primary
133
134 if name is None:
135 name = data_utils.rand_name(self.__class__.__name__ + "-server")
136
137 vnic_type = CONF.network.port_vnic_type
138
139 # If vnic_type is configured create port for
140 # every network
141 if vnic_type:
142 ports = []
143
144 create_port_body = {'binding:vnic_type': vnic_type,
145 'namestart': 'port-smoke'}
146 if kwargs:
147 # Convert security group names to security group ids
148 # to pass to create_port
149 if 'security_groups' in kwargs:
150 security_groups = \
151 clients.security_groups_client.list_security_groups(
152 ).get('security_groups')
153 sec_dict = dict([(s['name'], s['id'])
154 for s in security_groups])
155
156 sec_groups_names = [s['name'] for s in kwargs.pop(
157 'security_groups')]
158 security_groups_ids = [sec_dict[s]
159 for s in sec_groups_names]
160
161 if security_groups_ids:
162 create_port_body[
163 'security_groups'] = security_groups_ids
164 networks = kwargs.pop('networks', [])
165 else:
166 networks = []
167
168 # If there are no networks passed to us we look up
169 # for the project's private networks and create a port.
170 # The same behaviour as we would expect when passing
171 # the call to the clients with no networks
172 if not networks:
173 networks = clients.networks_client.list_networks(
174 **{'router:external': False, 'fields': 'id'})['networks']
175
176 # It's net['uuid'] if networks come from kwargs
177 # and net['id'] if they come from
178 # clients.networks_client.list_networks
179 for net in networks:
180 net_id = net.get('uuid', net.get('id'))
181 if 'port' not in net:
182 port = self._create_port(network_id=net_id,
183 client=clients.ports_client,
184 **create_port_body)
185 ports.append({'port': port['id']})
186 else:
187 ports.append({'port': net['port']})
188 if ports:
189 kwargs['networks'] = ports
190 self.ports = ports
191
192 tenant_network = self.get_tenant_network()
193
194 body, servers = compute.create_test_server(
195 clients,
196 tenant_network=tenant_network,
197 wait_until=wait_until,
198 name=name, flavor=flavor,
199 image_id=image_id, **kwargs)
200
201 self.addCleanup(waiters.wait_for_server_termination,
202 clients.servers_client, body['id'])
203 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
204 clients.servers_client.delete_server, body['id'])
205 server = clients.servers_client.show_server(body['id'])['server']
206 return server
diff --git a/watcher_tempest_plugin/tests/scenario/test_execute_actuator.py b/watcher_tempest_plugin/tests/scenario/test_execute_actuator.py
deleted file mode 100644
index fd4a18d..0000000
--- a/watcher_tempest_plugin/tests/scenario/test_execute_actuator.py
+++ /dev/null
@@ -1,340 +0,0 @@
1# -*- encoding: utf-8 -*-
2# Copyright (c) 2016 b<>com
3#
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14# implied.
15# See the License for the specific language governing permissions and
16# limitations under the License.
17
18from __future__ import unicode_literals
19
20import collections
21import functools
22
23from tempest import config
24from tempest.lib.common.utils import test_utils
25
26from watcher_tempest_plugin.tests.scenario import base
27
28CONF = config.CONF
29
30
31class TestExecuteActionsViaActuator(base.BaseInfraOptimScenarioTest):
32
33 scenarios = [
34 ("nop", {"actions": [
35 {"action_type": "nop",
36 "input_parameters": {
37 "message": "hello World"}}]}),
38 ("sleep", {"actions": [
39 {"action_type": "sleep",
40 "input_parameters": {
41 "duration": 1.0}}]}),
42 ("change_nova_service_state", {"actions": [
43 {"action_type": "change_nova_service_state",
44 "input_parameters": {
45 "state": "enabled"},
46 "filling_function":
47 "_prerequisite_param_for_"
48 "change_nova_service_state_action"}]}),
49 ("resize", {"actions": [
50 {"action_type": "resize",
51 "filling_function": "_prerequisite_param_for_resize_action"}]}),
52 ("migrate", {"actions": [
53 {"action_type": "migrate",
54 "input_parameters": {
55 "migration_type": "live"},
56 "filling_function": "_prerequisite_param_for_migrate_action"},
57 {"action_type": "migrate",
58 "filling_function": "_prerequisite_param_for_migrate_action"}]})
59 ]
60
61 @classmethod
62 def resource_setup(cls):
63 super(TestExecuteActionsViaActuator, cls).resource_setup()
64 if CONF.compute.min_compute_nodes < 2:
65 raise cls.skipException(
66 "Less than 2 compute nodes, skipping multinode tests.")
67 if not CONF.compute_feature_enabled.live_migration:
68 raise cls.skipException("Live migration is not enabled")
69
70 cls.initial_compute_nodes_setup = cls.get_compute_nodes_setup()
71 enabled_compute_nodes = [cn for cn in cls.initial_compute_nodes_setup
72 if cn.get('status') == 'enabled']
73
74 cls.wait_for_compute_node_setup()
75
76 if len(enabled_compute_nodes) < 2:
77 raise cls.skipException(
78 "Less than 2 compute nodes are enabled, "
79 "skipping multinode tests.")
80
81 @classmethod
82 def get_compute_nodes_setup(cls):
83 services_client = cls.mgr.services_client
84 available_services = services_client.list_services()['services']
85
86 return [srv for srv in available_services
87 if srv.get('binary') == 'nova-compute']
88
89 @classmethod
90 def wait_for_compute_node_setup(cls):
91
92 def _are_compute_nodes_setup():
93 try:
94 hypervisors_client = cls.mgr.hypervisor_client
95 hypervisors = hypervisors_client.list_hypervisors(
96 detail=True)['hypervisors']
97 available_hypervisors = set(
98 hyp['hypervisor_hostname'] for hyp in hypervisors)
99 available_services = set(
100 service['host']
101 for service in cls.get_compute_nodes_setup())
102
103 return (
104 available_hypervisors == available_services and
105 len(hypervisors) >= 2)
106 except Exception:
107 return False
108
109 assert test_utils.call_until_true(
110 func=_are_compute_nodes_setup,
111 duration=600,
112 sleep_for=2
113 )
114
115 @classmethod
116 def rollback_compute_nodes_status(cls):
117 current_compute_nodes_setup = cls.get_compute_nodes_setup()
118 for cn_setup in current_compute_nodes_setup:
119 cn_hostname = cn_setup.get('host')
120 matching_cns = [
121 cns for cns in cls.initial_compute_nodes_setup
122 if cns.get('host') == cn_hostname
123 ]
124 initial_cn_setup = matching_cns[0] # Should return a single result
125 if cn_setup.get('status') != initial_cn_setup.get('status'):
126 if initial_cn_setup.get('status') == 'enabled':
127 rollback_func = cls.mgr.services_client.enable_service
128 else:
129 rollback_func = cls.mgr.services_client.disable_service
130 rollback_func(binary='nova-compute', host=cn_hostname)
131
132 def _create_one_instance_per_host(self):
133 """Create 1 instance per compute node
134
135 This goes up to the min_compute_nodes threshold so that things don't
136 get crazy if you have 1000 compute nodes but set min to 3.
137 """
138 host_client = self.mgr.hosts_client
139 all_hosts = host_client.list_hosts()['hosts']
140 compute_nodes = [x for x in all_hosts if x['service'] == 'compute']
141
142 created_servers = []
143 for _ in compute_nodes[:CONF.compute.min_compute_nodes]:
144 # by getting to active state here, this means this has
145 # landed on the host in question.
146 created_servers.append(
147 self.create_server(image_id=CONF.compute.image_ref,
148 wait_until='ACTIVE',
149 clients=self.mgr))
150
151 return created_servers
152
153 def _get_flavors(self):
154 return self.mgr.flavors_client.list_flavors()['flavors']
155
156 def _prerequisite_param_for_migrate_action(self):
157 created_instances = self._create_one_instance_per_host()
158 instance = created_instances[0]
159 source_node = created_instances[0]["OS-EXT-SRV-ATTR:host"]
160 destination_node = created_instances[-1]["OS-EXT-SRV-ATTR:host"]
161
162 parameters = {
163 "resource_id": instance['id'],
164 "migration_type": "live",
165 "source_node": source_node,
166 "destination_node": destination_node
167 }
168
169 return parameters
170
171 def _prerequisite_param_for_resize_action(self):
172 created_instances = self._create_one_instance_per_host()
173 instance = created_instances[0]
174 current_flavor_id = instance['flavor']['id']
175
176 flavors = self._get_flavors()
177 new_flavors = [f for f in flavors if f['id'] != current_flavor_id]
178 new_flavor = new_flavors[0]
179
180 parameters = {
181 "resource_id": instance['id'],
182 "flavor": new_flavor['name']
183 }
184
185 return parameters
186
187 def _prerequisite_param_for_change_nova_service_state_action(self):
188 enabled_compute_nodes = [cn for cn in
189 self.initial_compute_nodes_setup
190 if cn.get('status') == 'enabled']
191 enabled_compute_node = enabled_compute_nodes[0]
192
193 parameters = {
194 "resource_id": enabled_compute_node['host'],
195 "state": "enabled"
196 }
197
198 return parameters
199
200 def _fill_actions(self, actions):
201 for action in actions:
202 filling_function_name = action.pop('filling_function', None)
203
204 if filling_function_name is not None:
205 filling_function = getattr(self, filling_function_name, None)
206
207 if filling_function is not None:
208 parameters = filling_function()
209
210 resource_id = parameters.pop('resource_id', None)
211
212 if resource_id is not None:
213 action['resource_id'] = resource_id
214
215 input_parameters = action.get('input_parameters', None)
216
217 if input_parameters is not None:
218 parameters.update(input_parameters)
219 input_parameters.update(parameters)
220 else:
221 action['input_parameters'] = parameters
222
223 def _execute_actions(self, actions):
224 self.wait_for_all_action_plans_to_finish()
225
226 _, goal = self.client.show_goal("unclassified")
227 _, strategy = self.client.show_strategy("actuator")
228 _, audit_template = self.create_audit_template(
229 goal['uuid'], strategy=strategy['uuid'])
230 _, audit = self.create_audit(
231 audit_template['uuid'], parameters={"actions": actions})
232
233 self.assertTrue(test_utils.call_until_true(
234 func=functools.partial(self.has_audit_succeeded, audit['uuid']),
235 duration=30,
236 sleep_for=.5
237 ))
238 _, action_plans = self.client.list_action_plans(
239 audit_uuid=audit['uuid'])
240 action_plan = action_plans['action_plans'][0]
241
242 _, action_plan = self.client.show_action_plan(action_plan['uuid'])
243
244 # Execute the action plan
245 _, updated_ap = self.client.start_action_plan(action_plan['uuid'])
246
247 self.assertTrue(test_utils.call_until_true(
248 func=functools.partial(
249 self.has_action_plan_finished, action_plan['uuid']),
250 duration=300,
251 sleep_for=1
252 ))
253 _, finished_ap = self.client.show_action_plan(action_plan['uuid'])
254 _, action_list = self.client.list_actions(
255 action_plan_uuid=finished_ap["uuid"])
256
257 self.assertIn(updated_ap['state'], ('PENDING', 'ONGOING'))
258 self.assertIn(finished_ap['state'], ('SUCCEEDED', 'SUPERSEDED'))
259
260 expected_action_counter = collections.Counter(
261 act['action_type'] for act in actions)
262 action_counter = collections.Counter(
263 act['action_type'] for act in action_list['actions'])
264
265 self.assertEqual(expected_action_counter, action_counter)
266
267 def test_execute_nop(self):
268 self.addCleanup(self.rollback_compute_nodes_status)
269
270 actions = [{
271 "action_type": "nop",
272 "input_parameters": {"message": "hello World"}}]
273 self._execute_actions(actions)
274
275 def test_execute_sleep(self):
276 self.addCleanup(self.rollback_compute_nodes_status)
277
278 actions = [
279 {"action_type": "sleep",
280 "input_parameters": {"duration": 1.0}}
281 ]
282 self._execute_actions(actions)
283
284 def test_execute_change_nova_service_state(self):
285 self.addCleanup(self.rollback_compute_nodes_status)
286
287 enabled_compute_nodes = [
288 cn for cn in self.initial_compute_nodes_setup
289 if cn.get('status') == 'enabled']
290
291 enabled_compute_node = enabled_compute_nodes[0]
292 actions = [
293 {"action_type": "change_nova_service_state",
294 "resource_id": enabled_compute_node['host'],
295 "input_parameters": {"state": "enabled"}}
296 ]
297 self._execute_actions(actions)
298
299 def test_execute_resize(self):
300 self.addCleanup(self.rollback_compute_nodes_status)
301
302 created_instances = self._create_one_instance_per_host()
303 instance = created_instances[0]
304 current_flavor_id = instance['flavor']['id']
305
306 flavors = self._get_flavors()
307 new_flavors = [f for f in flavors if f['id'] != current_flavor_id]
308 new_flavor = new_flavors[0]
309
310 actions = [
311 {"action_type": "resize",
312 "resource_id": instance['id'],
313 "input_parameters": {"flavor": new_flavor['name']}}
314 ]
315 self._execute_actions(actions)
316
317 def test_execute_migrate(self):
318 self.addCleanup(self.rollback_compute_nodes_status)
319
320 created_instances = self._create_one_instance_per_host()
321 instance = created_instances[0]
322 source_node = created_instances[0]["OS-EXT-SRV-ATTR:host"]
323 destination_node = created_instances[-1]["OS-EXT-SRV-ATTR:host"]
324 actions = [
325 {"action_type": "migrate",
326 "resource_id": instance['id'],
327 "input_parameters": {
328 "migration_type": "live",
329 "source_node": source_node,
330 "destination_node": destination_node}}
331 ]
332 self._execute_actions(actions)
333
334 def test_execute_scenarios(self):
335 self.addCleanup(self.rollback_compute_nodes_status)
336
337 for _, scenario in self.scenarios:
338 actions = scenario['actions']
339 self._fill_actions(actions)
340 self._execute_actions(actions)
diff --git a/watcher_tempest_plugin/tests/scenario/test_execute_basic_optim.py b/watcher_tempest_plugin/tests/scenario/test_execute_basic_optim.py
deleted file mode 100644
index b4b5e76..0000000
--- a/watcher_tempest_plugin/tests/scenario/test_execute_basic_optim.py
+++ /dev/null
@@ -1,191 +0,0 @@
1# -*- encoding: utf-8 -*-
2# Copyright (c) 2016 b<>com
3#
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14# implied.
15# See the License for the specific language governing permissions and
16# limitations under the License.
17
18from __future__ import unicode_literals
19
20import functools
21
22from tempest import config
23from tempest.lib.common.utils import test_utils
24
25from watcher_tempest_plugin.tests.scenario import base
26
27CONF = config.CONF
28
29
30class TestExecuteBasicStrategy(base.BaseInfraOptimScenarioTest):
31 """Tests for action plans"""
32
33 GOAL_NAME = "server_consolidation"
34
35 @classmethod
36 def skip_checks(cls):
37 super(TestExecuteBasicStrategy, cls).skip_checks()
38
39 @classmethod
40 def resource_setup(cls):
41 super(TestExecuteBasicStrategy, cls).resource_setup()
42 if CONF.compute.min_compute_nodes < 2:
43 raise cls.skipException(
44 "Less than 2 compute nodes, skipping multinode tests.")
45 if not CONF.compute_feature_enabled.live_migration:
46 raise cls.skipException("Live migration is not enabled")
47
48 cls.initial_compute_nodes_setup = cls.get_compute_nodes_setup()
49 enabled_compute_nodes = [cn for cn in cls.initial_compute_nodes_setup
50 if cn.get('status') == 'enabled']
51
52 cls.wait_for_compute_node_setup()
53
54 if len(enabled_compute_nodes) < 2:
55 raise cls.skipException(
56 "Less than 2 compute nodes are enabled, "
57 "skipping multinode tests.")
58
59 @classmethod
60 def get_compute_nodes_setup(cls):
61 services_client = cls.mgr.services_client
62 available_services = services_client.list_services()['services']
63
64 return [srv for srv in available_services
65 if srv.get('binary') == 'nova-compute']
66
67 @classmethod
68 def wait_for_compute_node_setup(cls):
69
70 def _are_compute_nodes_setup():
71 try:
72 hypervisors_client = cls.mgr.hypervisor_client
73 hypervisors = hypervisors_client.list_hypervisors(
74 detail=True)['hypervisors']
75 available_hypervisors = set(
76 hyp['hypervisor_hostname'] for hyp in hypervisors)
77 available_services = set(
78 service['host']
79 for service in cls.get_compute_nodes_setup())
80
81 return (
82 available_hypervisors == available_services and
83 len(hypervisors) >= 2)
84 except Exception:
85 return False
86
87 assert test_utils.call_until_true(
88 func=_are_compute_nodes_setup,
89 duration=600,
90 sleep_for=2
91 )
92
93 @classmethod
94 def rollback_compute_nodes_status(cls):
95 current_compute_nodes_setup = cls.get_compute_nodes_setup()
96 for cn_setup in current_compute_nodes_setup:
97 cn_hostname = cn_setup.get('host')
98 matching_cns = [
99 cns for cns in cls.initial_compute_nodes_setup
100 if cns.get('host') == cn_hostname
101 ]
102 initial_cn_setup = matching_cns[0] # Should return a single result
103 if cn_setup.get('status') != initial_cn_setup.get('status'):
104 if initial_cn_setup.get('status') == 'enabled':
105 rollback_func = cls.mgr.services_client.enable_service
106 else:
107 rollback_func = cls.mgr.services_client.disable_service
108 rollback_func(binary='nova-compute', host=cn_hostname)
109
110 def _create_one_instance_per_host(self):
111 """Create 1 instance per compute node
112
113 This goes up to the min_compute_nodes threshold so that things don't
114 get crazy if you have 1000 compute nodes but set min to 3.
115 """
116 host_client = self.mgr.hosts_client
117 all_hosts = host_client.list_hosts()['hosts']
118 compute_nodes = [x for x in all_hosts if x['service'] == 'compute']
119
120 for idx, _ in enumerate(
121 compute_nodes[:CONF.compute.min_compute_nodes], start=1):
122 # by getting to active state here, this means this has
123 # landed on the host in question.
124 self.create_server(
125 name="instance-%d" % idx,
126 image_id=CONF.compute.image_ref,
127 wait_until='ACTIVE',
128 clients=self.mgr)
129
130 def test_execute_basic_action_plan(self):
131 """Execute an action plan based on the BASIC strategy
132
133 - create an audit template with the basic strategy
134 - run the audit to create an action plan
135 - get the action plan
136 - run the action plan
137 - get results and make sure it succeeded
138 """
139 self.addCleanup(self.rollback_compute_nodes_status)
140 self._create_one_instance_per_host()
141
142 _, goal = self.client.show_goal(self.GOAL_NAME)
143 _, strategy = self.client.show_strategy("basic")
144 _, audit_template = self.create_audit_template(
145 goal['uuid'], strategy=strategy['uuid'])
146 _, audit = self.create_audit(audit_template['uuid'])
147
148 try:
149 self.assertTrue(test_utils.call_until_true(
150 func=functools.partial(
151 self.has_audit_finished, audit['uuid']),
152 duration=600,
153 sleep_for=2
154 ))
155 except ValueError:
156 self.fail("The audit has failed!")
157
158 _, finished_audit = self.client.show_audit(audit['uuid'])
159 if finished_audit.get('state') in ('FAILED', 'CANCELLED', 'SUSPENDED'):
160 self.fail("The audit ended in unexpected state: %s!"
161 % finished_audit.get('state'))
162
163 _, action_plans = self.client.list_action_plans(
164 audit_uuid=audit['uuid'])
165 action_plan = action_plans['action_plans'][0]
166
167 _, action_plan = self.client.show_action_plan(action_plan['uuid'])
168
169 if action_plan['state'] in ('SUPERSEDED', 'SUCCEEDED'):
170 # This means the action plan is superseded so we cannot trigger it,
171 # or it is empty.
172 return
173
174 # Execute the action by changing its state to PENDING
175 _, updated_ap = self.client.start_action_plan(action_plan['uuid'])
176
177 self.assertTrue(test_utils.call_until_true(
178 func=functools.partial(
179 self.has_action_plan_finished, action_plan['uuid']),
180 duration=600,
181 sleep_for=2
182 ))
183 _, finished_ap = self.client.show_action_plan(action_plan['uuid'])
184 _, action_list = self.client.list_actions(
185 action_plan_uuid=finished_ap["uuid"])
186
187 self.assertIn(updated_ap['state'], ('PENDING', 'ONGOING'))
188 self.assertIn(finished_ap['state'], ('SUCCEEDED', 'SUPERSEDED'))
189
190 for action in action_list['actions']:
191 self.assertEqual('SUCCEEDED', action.get('state'))
diff --git a/watcher_tempest_plugin/tests/scenario/test_execute_dummy_optim.py b/watcher_tempest_plugin/tests/scenario/test_execute_dummy_optim.py
deleted file mode 100644
index 33b108a..0000000
--- a/watcher_tempest_plugin/tests/scenario/test_execute_dummy_optim.py
+++ /dev/null
@@ -1,85 +0,0 @@
1# -*- encoding: utf-8 -*-
2# Copyright (c) 2016 b<>com
3#
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14# implied.
15# See the License for the specific language governing permissions and
16# limitations under the License.
17
18from __future__ import unicode_literals
19
20import collections
21import functools
22
23from tempest.lib.common.utils import test_utils
24
25from watcher_tempest_plugin.tests.scenario import base
26
27
28class TestExecuteDummyStrategy(base.BaseInfraOptimScenarioTest):
29 """Tests for action plans"""
30
31 def test_execute_dummy_action_plan(self):
32 """Execute an action plan based on the 'dummy' strategy
33
34 - create an audit template with the 'dummy' strategy
35 - run the audit to create an action plan
36 - get the action plan
37 - run the action plan
38 - get results and make sure it succeeded
39 """
40 _, goal = self.client.show_goal("dummy")
41 _, audit_template = self.create_audit_template(goal['uuid'])
42 _, audit = self.create_audit(audit_template['uuid'])
43
44 self.assertTrue(test_utils.call_until_true(
45 func=functools.partial(self.has_audit_finished, audit['uuid']),
46 duration=30,
47 sleep_for=.5
48 ))
49
50 self.assertTrue(self.has_audit_succeeded(audit['uuid']))
51
52 _, action_plans = self.client.list_action_plans(
53 audit_uuid=audit['uuid'])
54 action_plan = action_plans['action_plans'][0]
55
56 _, action_plan = self.client.show_action_plan(action_plan['uuid'])
57
58 if action_plan['state'] in ['SUPERSEDED', 'SUCCEEDED']:
59 # This means the action plan is superseded so we cannot trigger it,
60 # or it is empty.
61 return
62
63 # Execute the action by changing its state to PENDING
64 _, updated_ap = self.client.start_action_plan(action_plan['uuid'])
65
66 self.assertTrue(test_utils.call_until_true(
67 func=functools.partial(
68 self.has_action_plan_finished, action_plan['uuid']),
69 duration=30,
70 sleep_for=.5
71 ))
72 _, finished_ap = self.client.show_action_plan(action_plan['uuid'])
73 _, action_list = self.client.list_actions(
74 action_plan_uuid=finished_ap["uuid"])
75
76 action_counter = collections.Counter(
77 act['action_type'] for act in action_list['actions'])
78
79 self.assertIn(updated_ap['state'], ('PENDING', 'ONGOING'))
80 self.assertIn(finished_ap['state'], ('SUCCEEDED', 'SUPERSEDED'))
81
82 # A dummy strategy generates 2 "nop" actions and 1 "sleep" action
83 self.assertEqual(3, len(action_list['actions']))
84 self.assertEqual(2, action_counter.get("nop"))
85 self.assertEqual(1, action_counter.get("sleep"))
diff --git a/watcher_tempest_plugin/tests/scenario/test_execute_workload_balancing.py b/watcher_tempest_plugin/tests/scenario/test_execute_workload_balancing.py
deleted file mode 100644
index 8594e94..0000000
--- a/watcher_tempest_plugin/tests/scenario/test_execute_workload_balancing.py
+++ /dev/null
@@ -1,198 +0,0 @@
1# -*- encoding: utf-8 -*-
2# Copyright (c) 2016 b<>com
3#
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14# implied.
15# See the License for the specific language governing permissions and
16# limitations under the License.
17
18from __future__ import unicode_literals
19
20import functools
21
22from oslo_log import log
23from tempest import config
24from tempest.lib.common.utils import test_utils
25
26from watcher_tempest_plugin.tests.scenario import base
27
28CONF = config.CONF
29LOG = log.getLogger(__name__)
30
31
32class TestExecuteWorkloadBalancingStrategy(base.BaseInfraOptimScenarioTest):
33 """Tests for action plans"""
34
35 GOAL = "workload_balancing"
36
37 @classmethod
38 def skip_checks(cls):
39 super(TestExecuteWorkloadBalancingStrategy, cls).skip_checks()
40
41 @classmethod
42 def resource_setup(cls):
43 super(TestExecuteWorkloadBalancingStrategy, cls).resource_setup()
44 if CONF.compute.min_compute_nodes < 2:
45 raise cls.skipException(
46 "Less than 2 compute nodes, skipping multinode tests.")
47 if not CONF.compute_feature_enabled.live_migration:
48 raise cls.skipException("Live migration is not enabled")
49
50 cls.initial_compute_nodes_setup = cls.get_compute_nodes_setup()
51 enabled_compute_nodes = [cn for cn in cls.initial_compute_nodes_setup
52 if cn.get('status') == 'enabled']
53
54 cls.wait_for_compute_node_setup()
55
56 if len(enabled_compute_nodes) < 2:
57 raise cls.skipException(
58 "Less than 2 compute nodes are enabled, "
59 "skipping multinode tests.")
60
61 @classmethod
62 def get_hypervisors_setup(cls):
63 hypervisors_client = cls.mgr.hypervisor_client
64 hypervisors = hypervisors_client.list_hypervisors(
65 detail=True)['hypervisors']
66 return hypervisors
67
68 @classmethod
69 def get_compute_nodes_setup(cls):
70 services_client = cls.mgr.services_client
71 available_services = services_client.list_services()['services']
72
73 return [srv for srv in available_services
74 if srv.get('binary') == 'nova-compute']
75
76 def _migrate_server_to(self, server_id, dest_host, volume_backed=False):
77 kwargs = dict()
78 kwargs['disk_over_commit'] = False
79 block_migration = (CONF.compute_feature_enabled.
80 block_migration_for_live_migration and
81 not volume_backed)
82 body = self.mgr.servers_client.live_migrate_server(
83 server_id, host=dest_host, block_migration=block_migration,
84 **kwargs)
85 return body
86
87 @classmethod
88 def wait_for_compute_node_setup(cls):
89
90 def _are_compute_nodes_setup():
91 try:
92 hypervisors = cls.get_hypervisors_setup()
93 available_hypervisors = set(
94 hyp['hypervisor_hostname'] for hyp in hypervisors
95 if hyp['state'] == 'up')
96 available_services = set(
97 service['host']
98 for service in cls.get_compute_nodes_setup()
99 if service['state'] == 'up')
100 return (
101 len(available_hypervisors) == len(available_services) and
102 len(hypervisors) >= 2)
103 except Exception as exc:
104 LOG.exception(exc)
105 return False
106
107 assert test_utils.call_until_true(
108 func=_are_compute_nodes_setup,
109 duration=600,
110 sleep_for=2
111 )
112
113 @classmethod
114 def rollback_compute_nodes_status(cls):
115 current_compute_nodes_setup = cls.get_compute_nodes_setup()
116 for cn_setup in current_compute_nodes_setup:
117 cn_hostname = cn_setup.get('host')
118 matching_cns = [
119 cns for cns in cls.initial_compute_nodes_setup
120 if cns.get('host') == cn_hostname
121 ]
122 initial_cn_setup = matching_cns[0] # Should return a single result
123 if cn_setup.get('status') != initial_cn_setup.get('status'):
124 if initial_cn_setup.get('status') == 'enabled':
125 rollback_func = cls.mgr.services_client.enable_service
126 else:
127 rollback_func = cls.mgr.services_client.disable_service
128 rollback_func(binary='nova-compute', host=cn_hostname)
129
130 def _create_one_instance_per_host(self):
131 """Create 1 instance per compute node
132
133 This goes up to the min_compute_nodes threshold so that things don't
134 get crazy if you have 1000 compute nodes but set min to 3.
135 """
136 host_client = self.mgr.hosts_client
137 all_hosts = host_client.list_hosts()['hosts']
138 compute_nodes = [x for x in all_hosts if x['service'] == 'compute']
139
140 created_instances = []
141 for _ in compute_nodes[:CONF.compute.min_compute_nodes]:
142 # by getting to active state here, this means this has
143 # landed on the host in question.
144 created_instances.append(
145 self.create_server(image_id=CONF.compute.image_ref,
146 wait_until='ACTIVE', clients=self.mgr))
147 return created_instances
148
149 def _pack_all_created_instances_on_one_host(self, instances):
150 hypervisors = [
151 hyp['hypervisor_hostname'] for hyp in self.get_hypervisors_setup()
152 if hyp['state'] == 'up']
153 node = hypervisors[0]
154 for instance in instances:
155 if instance.get('OS-EXT-SRV-ATTR:hypervisor_hostname') != node:
156 self._migrate_server_to(instance['id'], node)
157
158 def test_execute_workload_stabilization(self):
159 """Execute an action plan using the workload_stabilization strategy"""
160 self.addCleanup(self.rollback_compute_nodes_status)
161 instances = self._create_one_instance_per_host()
162 self._pack_all_created_instances_on_one_host(instances)
163
164 audit_parameters = {
165 "metrics": ["cpu_util"],
166 "thresholds": {"cpu_util": 0.2},
167 "weights": {"cpu_util_weight": 1.0},
168 "instance_metrics": {"cpu_util": "compute.node.cpu.percent"}}
169
170 _, goal = self.client.show_goal(self.GOAL)
171 _, strategy = self.client.show_strategy("workload_stabilization")
172 _, audit_template = self.create_audit_template(
173 goal['uuid'], strategy=strategy['uuid'])
174 _, audit = self.create_audit(
175 audit_template['uuid'], parameters=audit_parameters)
176
177 try:
178 self.assertTrue(test_utils.call_until_true(
179 func=functools.partial(
180 self.has_audit_finished, audit['uuid']),
181 duration=600,
182 sleep_for=2
183 ))
184 except ValueError:
185 self.fail("The audit has failed!")
186
187 _, finished_audit = self.client.show_audit(audit['uuid'])
188 if finished_audit.get('state') in ('FAILED', 'CANCELLED'):
189 self.fail("The audit ended in unexpected state: %s!" %
190 finished_audit.get('state'))
191
192 _, action_plans = self.client.list_action_plans(
193 audit_uuid=audit['uuid'])
194 action_plan = action_plans['action_plans'][0]
195
196 _, action_plan = self.client.show_action_plan(action_plan['uuid'])
197 _, action_list = self.client.list_actions(
198 action_plan_uuid=action_plan["uuid"])