From dc4a8868ae6f96019134665e88c93bb381d206a0 Mon Sep 17 00:00:00 2001 From: Roland Hochmuth Date: Sat, 26 Sep 2015 00:10:43 -0600 Subject: [PATCH] Add Monasca Tempest Tests To run the tests see monasca_tempest_tests/README.md. Change-Id: I7d1df1b88b63ccc2f1a66deaf439c032b1175d99 --- .testr.conf | 2 +- monasca_tempest_tests/README.md | 102 ++++ monasca_tempest_tests/__init__.py | 0 monasca_tempest_tests/clients.py | 23 + monasca_tempest_tests/config.py | 45 ++ monasca_tempest_tests/plugin.py | 40 ++ monasca_tempest_tests/services/__init__.py | 0 .../services/monasca_client.py | 275 ++++++++++ monasca_tempest_tests/tests/__init__.py | 0 monasca_tempest_tests/tests/api/__init__.py | 0 monasca_tempest_tests/tests/api/base.py | 52 ++ monasca_tempest_tests/tests/api/constants.py | 37 ++ monasca_tempest_tests/tests/api/helpers.py | 77 +++ .../tests/api/test_alarm_definitions.py | 483 ++++++++++++++++++ .../tests/api/test_alarms.py | 281 ++++++++++ .../tests/api/test_alarms_state_history.py | 212 ++++++++ .../tests/api/test_measurements.py | 221 ++++++++ .../tests/api/test_metrics.py | 236 +++++++++ .../tests/api/test_metrics_names.py | 47 ++ .../tests/api/test_notification_methods.py | 297 +++++++++++ .../tests/api/test_statistics.py | 301 +++++++++++ .../tests/api/test_versions.py | 51 ++ setup.cfg | 8 +- tox.ini | 2 +- 24 files changed, 2788 insertions(+), 4 deletions(-) create mode 100644 monasca_tempest_tests/README.md create mode 100644 monasca_tempest_tests/__init__.py create mode 100644 monasca_tempest_tests/clients.py create mode 100644 monasca_tempest_tests/config.py create mode 100644 monasca_tempest_tests/plugin.py create mode 100644 monasca_tempest_tests/services/__init__.py create mode 100644 monasca_tempest_tests/services/monasca_client.py create mode 100644 monasca_tempest_tests/tests/__init__.py create mode 100644 monasca_tempest_tests/tests/api/__init__.py create mode 100644 monasca_tempest_tests/tests/api/base.py create mode 100644 monasca_tempest_tests/tests/api/constants.py create mode 100644 monasca_tempest_tests/tests/api/helpers.py create mode 100644 monasca_tempest_tests/tests/api/test_alarm_definitions.py create mode 100644 monasca_tempest_tests/tests/api/test_alarms.py create mode 100644 monasca_tempest_tests/tests/api/test_alarms_state_history.py create mode 100644 monasca_tempest_tests/tests/api/test_measurements.py create mode 100644 monasca_tempest_tests/tests/api/test_metrics.py create mode 100644 monasca_tempest_tests/tests/api/test_metrics_names.py create mode 100644 monasca_tempest_tests/tests/api/test_notification_methods.py create mode 100644 monasca_tempest_tests/tests/api/test_statistics.py create mode 100644 monasca_tempest_tests/tests/api/test_versions.py diff --git a/.testr.conf b/.testr.conf index c46f91289..1414396c9 100644 --- a/.testr.conf +++ b/.testr.conf @@ -2,7 +2,7 @@ test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \ OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \ OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \ - ${PYTHON:-python} -m subunit.run discover monasca_api $LISTOPT $IDOPTION + ${PYTHON:-python} -m subunit.run discover -t . ./monasca_api/tests $LISTOPT $IDOPTION test_id_option=--load-list $IDFILE test_list_option=--list diff --git a/monasca_tempest_tests/README.md b/monasca_tempest_tests/README.md new file mode 100644 index 000000000..6a030c162 --- /dev/null +++ b/monasca_tempest_tests/README.md @@ -0,0 +1,102 @@ +# Introduction +The Monasca Tempest Tests use the [OpenStack Tempest Plugin Interface](http://docs.openstack.org/developer/tempest/plugin.html). This README describes how to configure and run them using a variety of methods. +Currently monasca-vagrant is used to run tests. This document will be updated when switch the environment to DevStack. + +# Configuring to run the Monasca Tempest Tests +1. Clone the OpenStack Tempest repo, and cd to it. + ``` + git clone https://github.com/openstack/tempest.git + cd tempest + ``` +2. Create a virtualenv for running the Tempest tests and activate it. For example in the Tempest root dir + ``` + virtualenv .venv + source .venv/bin/activate + ``` +3. Install the Tempest requirements in the virtualenv. + ``` + pip install -r requirements.txt -r test-requirements.txt + pip install nose + ``` +4. Create ```etc/tempest.conf``` and ```etc/logging.conf``` in the Tempest root dir. I believe the file ```etc/tempest.conf.sample``` can be copied to ```etc/tempest.conf```. Similarly for ```logging.conf```. Add the following sections to ```tempest.conf``` for testing using the monasca-vagrant environment. + ``` + [identity] + + username = mini-mon + password = password + tenant_name = mini-mon + domain_name = default + admin_username = admin + admin_password = admin + admin_domain_name = default + admin_tenant_name = admin + alt_username = mini-mon + alt_password = password + alt_tenant_name = mini-mon + use_ssl = False + auth_version = v3 + uri = http://192.168.10.5:5000/v2.0/ + uri_v3 = http://192.168.10.5:35357/v3/ + + [auth] + + allow_tenant_isolation = true + tempest_roles = monasca-user + ``` +5. Clone the monasca-api repo. +6. Install the monasca-api in your venv, which will also register +the Monasca Tempest Plugin as, monasca_tests. + ``` + python setup.py install + ``` +See the [OpenStack Tempest Plugin Interface](http://docs.openstack.org/developer/tempest/plugin.html), for more details on Tempest Plugins and the plugin registration process. + +# Running the Monasca Tempest Tests +The Monasca Tempest Tests can be run using a variety of methods including: +1. [Testr](https://wiki.openstack.org/wiki/Testr) +2. [Os-testr](http://docs.openstack.org/developer/os-testr/) +3. [PyCharm]([Os-testr](https://www.jetbrains.com/pycharm/) + +## Run the tests from the CLI using testr +[Testr](https://wiki.openstack.org/wiki/Testr) is a test runner that can be used to run the Tempest tests. +1. In the Tempest root dir, create a list of the Monasca Tempest Tests in a file. + ``` + testr list-tests monasca_tempest_tests > monasca_tempest_tests + ``` +2. Run the tests using testr + ``` + testr run --load-list=monasca_tempest_tests + ``` +You can also use testr to create a list of specific tests for your needs. + +## Run the tests from the CLI using os-testr (no file necessary) +[Os-testr](http://docs.openstack.org/developer/os-testr/) is a test wrapper that can be used to run the Monasca Tempest tests. +In the Tempest root dir: + ``` + ostestr --regex monasca_tempest_tests + ``` +## Running/Debugging the Monasca Tempest Tests in PyCharm +Assuming that you have already created a PyCharm project for the ```monasca-api``` do the following: +1. In PyCharm, Edit Configurations and add a new Python tests configuration by selecting Python tests->Nosetests. +2. Name the test. For example TestVersions. +3. Set the path to the script with the tests to run. For example, ~/repos/monasca-api/monasca_tempest_tests/api/test_versions.py +4. Set the name of the Class to test. For example TestVersions. +5. Set the working directory to your local root Tempest repo. For example, ~/repos/tempest. +6. Select the Python interpreter for your project to be the same as the one virtualenv created above. For example, ~/repos/tempest/.venv +7. Run the tests. You should also be able to debug them. +8. Step and repeat for other tests. + +# References +This section provides a few additional references that might be useful: +* [Tempest - The OpenStack Integration Test Suite](http://docs.openstack.org/developer/tempest/overview.html#quickstart) +* [Tempest Configuration Guide](https://github.com/openstack/tempest/blob/master/doc/source/configuration.rst#id1) +* [OpenStack Tempest Plugin Interface](http://docs.openstack.org/developer/tempest/plugin.html) + +In addition to the above references, another source of information is the following OpenStack projects: +* [Manila Tempest Tests](https://github.com/openstack/manila/tree/master/manila_tempest_tests) +* [Congress Tempest Tests](https://github.com/openstack/congress/tree/master/congress_tempest_tests). +In particular, the Manila Tempest Tests were used as a reference implementation to develop the Monasca Tempest Tests. There is also a wiki [HOWTO use tempest with manila](https://wiki.openstack.org/wiki/Manila/docs/HOWTO_use_tempest_with_manila) that might be useful for Monasca too. + +# Issues +* Update documentation for testing using Devstack when available. +* Consider changing from monasca_tempest_tests to monasca_api_tempest_tests. \ No newline at end of file diff --git a/monasca_tempest_tests/__init__.py b/monasca_tempest_tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/monasca_tempest_tests/clients.py b/monasca_tempest_tests/clients.py new file mode 100644 index 000000000..29ad177ef --- /dev/null +++ b/monasca_tempest_tests/clients.py @@ -0,0 +1,23 @@ +# (C) Copyright 2015 Hewlett Packard Enterprise Development Company LP +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from tempest import clients + +from monasca_tempest_tests.services import monasca_client + + +class Manager(clients.Manager): + def __init__(self, credentials=None, service=None): + super(Manager, self).__init__(credentials, service) + self.monasca_client = monasca_client.MonascaClient(self.auth_provider) diff --git a/monasca_tempest_tests/config.py b/monasca_tempest_tests/config.py new file mode 100644 index 000000000..e321edb65 --- /dev/null +++ b/monasca_tempest_tests/config.py @@ -0,0 +1,45 @@ +# (C) Copyright 2015 Hewlett Packard Enterprise Development Company LP +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from oslo_config import cfg + + +service_available_group = cfg.OptGroup(name="service_available", + title="Available OpenStack Services") + +ServiceAvailableGroup = [ + cfg.BoolOpt("monasca", + default=True, + help="Whether or not Monasca is expected to be available"), +] + +monitoring_group = cfg.OptGroup(name="monitoring", + title="Monitoring Service Options") + +MonitoringGroup = [ + cfg.StrOpt("region", + default="", + help="The monitoring region name to use. If empty, the value " + "of identity.region is used instead. If no such region " + "is found in the service catalog, the first found one is " + "used."), + cfg.StrOpt("catalog_type", + default="monitoring", + help="Catalog type of the monitoring service."), + cfg.StrOpt('endpoint_type', + default='publicURL', + choices=['public', 'admin', 'internal', + 'publicURL', 'adminURL', 'internalURL'], + help="The endpoint type to use for the monitoring service.") +] diff --git a/monasca_tempest_tests/plugin.py b/monasca_tempest_tests/plugin.py new file mode 100644 index 000000000..77f0d8569 --- /dev/null +++ b/monasca_tempest_tests/plugin.py @@ -0,0 +1,40 @@ +# (C) Copyright 2015 Hewlett Packard Enterprise Development Company LP +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import os + +from tempest import config +from tempest.test_discover import plugins + +from monasca_tempest_tests import config as config_monitoring + + +class MonascaTempestPlugin(plugins.TempestPlugin): + def load_tests(self): + base_path = os.path.split(os.path.dirname( + os.path.abspath(__file__)))[0] + test_dir = "monasca_tempest_tests/tests" + full_test_dir = os.path.join(base_path, test_dir) + return full_test_dir, base_path + + def register_opts(self, conf): + config.register_opt_group( + conf, config_monitoring.service_available_group, + config_monitoring.ServiceAvailableGroup) + config.register_opt_group(conf, config_monitoring.monitoring_group, + config_monitoring.MonitoringGroup) + + def get_opt_lists(self): + return [(config_monitoring.monitoring_group.name, + config_monitoring.MonitoringGroup)] diff --git a/monasca_tempest_tests/services/__init__.py b/monasca_tempest_tests/services/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/monasca_tempest_tests/services/monasca_client.py b/monasca_tempest_tests/services/monasca_client.py new file mode 100644 index 000000000..503e4ac36 --- /dev/null +++ b/monasca_tempest_tests/services/monasca_client.py @@ -0,0 +1,275 @@ +# (C) Copyright 2015 Hewlett Packard Enterprise Development Company LP +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +from oslo_serialization import jsonutils as json + +from tempest import config +from tempest_lib.common import rest_client + +CONF = config.CONF + + +class MonascaClient(rest_client.RestClient): + + def __init__(self, auth_provider): + super(MonascaClient, self).__init__( + auth_provider, + CONF.monitoring.catalog_type, + CONF.monitoring.region or CONF.identity.region, + endpoint_type=CONF.monitoring.endpoint_type) + + def get_version(self): + resp, response_body = self.get('') + return resp, response_body + + def create_metrics(self, metrics): + uri = 'metrics' + request_body = json.dumps(metrics) + resp, response_body = self.post(uri, request_body) + return resp, response_body + + def list_metrics(self, query_params=None): + uri = 'metrics' + if query_params is not None: + uri = uri + query_params + resp, response_body = self.get(uri) + return resp, json.loads(response_body) + + def list_metrics_names(self, query_params=None): + uri = 'metrics/names' + if query_params is not None: + uri = uri + query_params + resp, response_body = self.get(uri) + return resp, json.loads(response_body) + + def list_measurements(self, query_params=None): + uri = 'metrics/measurements' + if query_params is not None: + uri = uri + query_params + resp, response_body = self.get(uri) + return resp, json.loads(response_body) + + def list_statistics(self, query_params=None): + uri = 'metrics/statistics' + if query_params is not None: + uri = uri + query_params + resp, response_body = self.get(uri) + return resp, json.loads(response_body) + + def create_notifications(self, notification): + uri = 'notification-methods' + request_body = json.dumps(notification) + resp, response_body = self.post(uri, request_body) + return resp, json.loads(response_body) + + def create_notification_method(self, + name=None, + type=None, + address=None): + uri = 'notification-methods' + request_body = {} + if name is not None: + request_body['name'] = name + if type is not None: + request_body['type'] = type + if address is not None: + request_body['address'] = address + resp, response_body = self.post(uri, json.dumps(request_body)) + return resp, json.loads(response_body) + + def delete_notification_method(self, id): + uri = 'notification-methods/' + id + resp, response_body = self.delete(uri) + return resp, response_body + + def get_notification_method(self, id): + uri = 'notification-methods/' + id + resp, response_body = self.get(uri) + return resp, json.loads(response_body) + + def list_notification_methods(self, query_params=None): + uri = 'notification-methods' + if query_params is not None: + uri = uri + query_params + resp, response_body = self.get(uri) + return resp, json.loads(response_body) + + def update_notification_method(self, + id, + name=None, + type=None, + address=None): + uri = 'notification-methods/' + id + request_body = {} + if name is not None: + request_body['name'] = name + if type is not None: + request_body['type'] = type + if address is not None: + request_body['address'] = address + resp, response_body = self.put(uri, json.dumps(request_body)) + return resp, json.loads(response_body) + + def create_alarm_definitions(self, alarm_definitions): + uri = 'alarm-definitions' + request_body = json.dumps(alarm_definitions) + resp, response_body = self.post(uri, request_body) + return resp, json.loads(response_body) + + def list_alarm_definitions(self, query_params=None): + uri = 'alarm-definitions' + if query_params is not None: + uri = uri + query_params + resp, response_body = self.get(uri) + return resp, json.loads(response_body) + + def get_alarm_definition(self, id): + uri = 'alarm-definitions/' + id + resp, response_body = self.get(uri) + return resp, json.loads(response_body) + + def delete_alarm_definition(self, id): + uri = 'alarm-definitions/' + id + resp, response_body = self.delete(uri) + return resp, response_body + + def update_alarm_definition(self, id, name, expression, description=None, + actions_enabled=None, match_by=None, + severity=None, alarm_actions=None, + ok_actions=None, undetermined_actions=None, + **kwargs): + uri = 'alarm-definitions/' + id + request_body = {} + request_body['name'] = name + request_body['expression'] = expression + + if description is not None: + request_body['description'] = description + if actions_enabled is not None: + request_body['actions_enabled'] = actions_enabled + if match_by is not None: + request_body['match_by'] = match_by + if severity is not None: + request_body['severity'] = severity + if alarm_actions is not None: + request_body['alarm_actions'] = alarm_actions + if ok_actions is not None: + request_body['ok_actions'] = ok_actions + if undetermined_actions is not None: + request_body['undetermined_actions'] = undetermined_actions + + for key, value in kwargs.iteritems(): + request_body[key] = value + + resp, response_body = self.patch(uri, json.dumps(request_body)) + return resp, json.loads(response_body) + + def patch_alarm_definition(self, + id, + name=None, + description=None, + expression=None, + actions_enabled=None, + match_by=None, + severity=None, + alarm_actions=None, + ok_actions=None, + undetermined_actions=None, + **kwargs): + uri = 'alarm-definitions/' + id + request_body = {} + if name is not None: + request_body['name'] = name + if description is not None: + request_body['description'] = description + if expression is not None: + request_body['expression'] = expression + if actions_enabled is not None: + request_body['actions_enabled'] = actions_enabled + if match_by is not None: + request_body['match_by'] = match_by + if severity is not None: + request_body['severity'] = severity + if alarm_actions is not None: + request_body['alarm_actions'] = alarm_actions + if ok_actions is not None: + request_body['ok_actions'] = ok_actions + if undetermined_actions is not None: + request_body['undetermined_actions'] = undetermined_actions + + for key, value in kwargs.iteritems(): + request_body[key] = value + + resp, response_body = self.patch(uri, json.dumps(request_body)) + return resp, json.loads(response_body) + + def list_alarms(self, query_params=None): + uri = 'alarms' + if query_params is not None: + uri = uri + query_params + resp, response_body = self.get(uri) + return resp, json.loads(response_body) + + def get_alarm(self, id): + uri = 'alarms/' + id + resp, response_body = self.get(uri) + return resp, json.loads(response_body) + + def delete_alarm(self, id): + uri = 'alarms/' + id + resp, response_body = self.delete(uri) + return resp, response_body + + def update_alarm(self, id, state, lifecycle_state, link, **kwargs): + uri = 'alarms/' + id + request_body = {} + request_body['state'] = state + request_body['lifecycle_state'] = lifecycle_state + request_body['link'] = link + + for key, value in kwargs.iteritems(): + request_body[key] = value + + resp, response_body = self.patch(uri, json.dumps(request_body)) + return resp, json.loads(response_body) + + def patch_alarm(self, id, state=None, lifecycle_state=None, link=None, + **kwargs): + uri = 'alarms/' + id + request_body = {} + if state is not None: + request_body['state'] = state + if lifecycle_state is not None: + request_body['lifecycle_state'] = lifecycle_state + if link is not None: + request_body['link'] = link + + for key, value in kwargs.iteritems(): + request_body[key] = value + + resp, response_body = self.patch(uri, json.dumps(request_body)) + return resp, json.loads(response_body) + + def list_alarms_state_history(self, query_params=None): + uri = 'alarms/state-history' + if query_params is not None: + uri = uri + query_params + resp, response_body = self.get(uri) + return resp, json.loads(response_body) + + def list_alarm_state_history(self, id, query_params=None): + uri = 'alarms/' + id + '/state-history' + if query_params is not None: + uri = uri + query_params + resp, response_body = self.get(uri) + return resp, json.loads(response_body) diff --git a/monasca_tempest_tests/tests/__init__.py b/monasca_tempest_tests/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/monasca_tempest_tests/tests/api/__init__.py b/monasca_tempest_tests/tests/api/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/monasca_tempest_tests/tests/api/base.py b/monasca_tempest_tests/tests/api/base.py new file mode 100644 index 000000000..ac3768978 --- /dev/null +++ b/monasca_tempest_tests/tests/api/base.py @@ -0,0 +1,52 @@ +# (C) Copyright 2015 Hewlett Packard Enterprise Development Company LP +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +from tempest import config +import tempest.test +from tempest_lib import exceptions + +from monasca_tempest_tests import clients + +CONF = config.CONF + + +class BaseMonascaTest(tempest.test.BaseTestCase): + """Base test case class for all Monasca API tests.""" + + @classmethod + def skip_checks(cls): + super(BaseMonascaTest, cls).skip_checks() + + @classmethod + def resource_setup(cls): + super(BaseMonascaTest, cls).resource_setup() + cls.os = clients.Manager() + cls.monasca_client = cls.os.monasca_client + + @staticmethod + def cleanup_resources(method, list_of_ids): + for resource_id in list_of_ids: + try: + method(resource_id) + except exceptions.NotFound: + pass + + @classmethod + def resource_cleanup(cls): + super(BaseMonascaTest, cls).resource_cleanup() + resp, response_body = cls.monasca_client.list_alarm_definitions() + elements = response_body['elements'] + for definition in elements: + id = definition['id'] + resp, response_body = cls.monasca_client. \ + delete_alarm_definition(id) diff --git a/monasca_tempest_tests/tests/api/constants.py b/monasca_tempest_tests/tests/api/constants.py new file mode 100644 index 000000000..586c284ad --- /dev/null +++ b/monasca_tempest_tests/tests/api/constants.py @@ -0,0 +1,37 @@ +# (C) Copyright 2015 Hewlett Packard Enterprise Development Company LP +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +MAX_METRIC_NAME_LENGTH = 255 +MAX_DIMENSION_KEY_LENGTH = 255 +MAX_DIMENSION_VALUE_LENGTH = 255 +INVALID_CHARS = "<>={}(),\"\;&" + +MAX_ALARM_DEFINITION_NAME_LENGTH = 255 +MAX_ALARM_DEFINITION_DESCRIPTION_LENGTH = 255 +MAX_ALARM_DEFINITION_ACTIONS_LENGTH = 50 + +MAX_NOTIFICATION_METHOD_NAME_LENGTH = 250 +MAX_NOTIFICATION_METHOD_TYPE_LENGTH = 100 +MAX_NOTIFICATION_METHOD_ADDRESS_LENGTH = 100 +INVALID_CHARS_NOTIFICATION = "<>={}(),\"\;&" + +MAX_LIST_MEASUREMENTS_NAME_LENGTH = 255 + +MAX_LIST_STATISTICS_NAME_LENGTH = 255 + +MAX_ALARM_LIFECYCLE_STATE_LENGTH = 50 +MAX_ALARM_METRIC_NAME_LENGTH = 255 +MAX_ALARM_METRIC_DIMENSIONS_KEY_LENGTH = 255 +MAX_ALARM_METRIC_DIMENSIONS_VALUE_LENGTH = 255 +MAX_ALARM_LINK_LENGTH = 512 diff --git a/monasca_tempest_tests/tests/api/helpers.py b/monasca_tempest_tests/tests/api/helpers.py new file mode 100644 index 000000000..f15ca0121 --- /dev/null +++ b/monasca_tempest_tests/tests/api/helpers.py @@ -0,0 +1,77 @@ +# (C) Copyright 2015 Hewlett Packard Enterprise Development Company LP +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import time + +from tempest.common.utils import data_utils + + +def create_metric(name='name-1', + dimensions={ + 'key-1': 'value-1', + 'key-2': 'value-2' + }, + timestamp=time.time() * 1000, + value=0.0): + metric = {} + if name is not None: + metric['name'] = name + if dimensions is not None: + metric['dimensions'] = dimensions + if timestamp is not None: + metric['timestamp'] = timestamp + if value is not None: + metric['value'] = value + return metric + + +def create_notification(name=data_utils.rand_name('notification-'), + type='EMAIL', + address='john.doe@domain.com'): + notification = {} + if name is not None: + notification['name'] = name + if type is not None: + notification['type'] = type + if address is not None: + notification['address'] = address + return notification + + +def create_alarm_definition(name=None, + description=None, + expression=None, + match_by=None, + severity=None, + alarm_actions=None, + ok_actions=None, + undetermined_actions=None): + alarm_definition = {} + if name is not None: + alarm_definition['name'] = name + if description is not None: + alarm_definition['description'] = description + if expression is not None: + alarm_definition['expression'] = expression + if match_by is not None: + alarm_definition['match_by'] = match_by + if severity is not None: + alarm_definition['severity'] = severity + if alarm_actions is not None: + alarm_definition['alarm_actions'] = alarm_actions + if ok_actions is not None: + alarm_definition['ok_actions'] = ok_actions + if undetermined_actions is not None: + alarm_definition['undetermined_actions'] = undetermined_actions + return alarm_definition diff --git a/monasca_tempest_tests/tests/api/test_alarm_definitions.py b/monasca_tempest_tests/tests/api/test_alarm_definitions.py new file mode 100644 index 000000000..e27936c34 --- /dev/null +++ b/monasca_tempest_tests/tests/api/test_alarm_definitions.py @@ -0,0 +1,483 @@ +# (C) Copyright 2015 Hewlett Packard Enterprise Development Company LP +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from monasca_tempest_tests.tests.api import base +from monasca_tempest_tests.tests.api import helpers +from tempest.common.utils import data_utils +from tempest import test +from tempest_lib import exceptions + +NUM_ALARM_DEFINITIONS = 2 + + +class TestAlarmDefinitions(base.BaseMonascaTest): + @classmethod + def resource_setup(cls): + super(TestAlarmDefinitions, cls).resource_setup() + cls.rule = {'expression': 'mem_total_mb > 0'} + for i in range(NUM_ALARM_DEFINITIONS): + alarm_definition = helpers.create_alarm_definition( + name='alarm-definition-' + str(i), + description='alarm definition description', + expression='avg(cpu_utilization{service=compute}) >= 1000') + cls.monasca_client.create_alarm_definitions(alarm_definition) + + @classmethod + def create_alarm_definition(cls): + # Create an alarm definition + name = data_utils.rand_name('alarm_definition') + expression = "max(cpu.system_perc) > 0" + alarm_definition = helpers.create_alarm_definition( + name=name, + description="description", + expression=expression) + return alarm_definition + + # Create + @test.attr(type="gate") + def test_create_alarm_definition(self): + # Create an alarm definition + name = data_utils.rand_name('alarm_definition') + expression = "max(cpu.system_perc) > 0" + alarm_definition = helpers.create_alarm_definition( + name=name, description="description", expression=expression) + resp, response_body = self.monasca_client.create_alarm_definitions( + alarm_definition) + self.assertEqual(201, resp.status) + self.assertEqual(name, response_body['name']) + alarm_def_id = response_body['id'] + self.assertEqual(expression, response_body['expression']) + + # Delete alarm and verify if deleted + resp, response_body = self.monasca_client.delete_alarm_definition( + alarm_def_id) + self.assertEqual(204, resp.status) + self.assertRaises(exceptions.NotFound, + self.monasca_client.get_alarm_definition, + alarm_def_id) + + @test.attr(type="gate") + def test_create_alarm_definition_with_notification(self): + notification_name = data_utils.rand_name('notification-') + notification_type = 'EMAIL' + u_address = 'root@localhost' + + resp, response_body = self.monasca_client.create_notification_method( + notification_name, type=notification_type, address=u_address) + self.assertEqual(201, resp.status) + self.assertEqual(notification_name, response_body['name']) + notification_id = response_body['id'] + + # Create an alarm definition + alarm_def_name = data_utils.rand_name('monitoring_alarm_definition') + expression = "mem_total_mb > 0" + alarm_definition = helpers.create_alarm_definition( + name=alarm_def_name, + expression=expression, + alarm_actions=notification_id, + ok_actions=notification_id, + undetermined_actions=notification_id, + severity="LOW") + resp, body = self.monasca_client.create_alarm_definitions( + alarm_definition) + self.assertEqual(201, resp.status) + self.assertEqual(alarm_def_name, body['name']) + alarm_def_id = body['id'] + self.assertEqual(expression, body['expression']) + self.assertEqual(notification_id, body['ok_actions'][0]) + self.assertEqual(notification_id, body['alarm_actions'][0]) + self.assertEqual(notification_id, body['undetermined_actions'][0]) + + # Delete alarm definition and verify if deleted + resp, body = self.monasca_client.delete_alarm_definition( + alarm_def_id) + self.assertEqual(204, resp.status) + self.assertRaises(exceptions.NotFound, + self.monasca_client.get_alarm_definition, + alarm_def_id) + + # Delete notification + resp, body = self.monasca_client.delete_notification_method( + notification_id) + self.assertEqual(204, resp.status) + + @test.attr(type="gate") + def test_create_alarm_definition_with_multiple_notifications(self): + notification_name1 = data_utils.rand_name('notification-') + notification_type1 = 'EMAIL' + address1 = 'root@localhost' + + notification_name2 = data_utils.rand_name('notification-') + notification_type2 = 'PAGERDUTY' + address2 = 'http://localhost.com' + + resp, body = self.monasca_client.create_notification_method( + notification_name1, type=notification_type1, address=address1) + self.assertEqual(201, resp.status) + self.assertEqual(notification_name1, body['name']) + notification_id1 = body['id'] + + resp, body = self.monasca_client.create_notification_method( + notification_name2, type=notification_type2, address=address2) + self.assertEqual(201, resp.status) + self.assertEqual(notification_name2, body['name']) + notification_id2 = body['id'] + + # Create an alarm definition + alarm_def_name = data_utils.rand_name('monitoring_alarm_definition') + alarm_definition = helpers.create_alarm_definition( + name=alarm_def_name, + expression="mem_total_mb > 0", + alarm_actions=[notification_id1, notification_id2], + ok_actions=[notification_id1, notification_id2], + severity="LOW") + resp, body = self.monasca_client.create_alarm_definitions( + alarm_definition) + self.assertEqual(201, resp.status) + self.assertEqual(alarm_def_name, body['name']) + alarm_def_id = body['id'] + self.assertEqual("mem_total_mb > 0", body['expression']) + + # Delete alarm definition and validate if deleted + resp, body = self.monasca_client.delete_alarm_definition( + alarm_def_id) + self.assertEqual(204, resp.status) + self.assertRaises(exceptions.NotFound, + self.monasca_client.get_alarm_definition, + alarm_def_id) + + # Delete notification 1 + resp, body = self.monasca_client.delete_notification_method( + notification_id1) + self.assertEqual(204, resp.status) + + # Delete notification 2 + resp, body = self.monasca_client.delete_notification_method( + notification_id2) + self.assertEqual(204, resp.status) + + @test.attr(type="gate") + def test_create_alarm_definition_with_url_in_expression(self): + notification_name = data_utils.rand_name('notification-') + notification_type = 'EMAIL' + u_address = 'root@localhost' + + resp, body = self.monasca_client.create_notification_method( + notification_name, type=notification_type, address=u_address) + self.assertEqual(201, resp.status) + self.assertEqual(notification_name, body['name']) + notification_id = body['id'] + + # Create an alarm definition + alarm_def_name = data_utils.rand_name('monitoring_alarm_definition') + alarm_definition = helpers.create_alarm_definition( + name=alarm_def_name, + expression="avg(mem_total_mb{url=https://www.google.com}) gt 0", + alarm_actions=notification_id, + ok_actions=notification_id, + severity="LOW") + resp, body = self.monasca_client.create_alarm_definitions( + alarm_definition) + self.assertEqual(201, resp.status) + self.assertEqual(alarm_def_name, body['name']) + alarm_def_id = body['id'] + self.assertEqual("avg(mem_total_mb{url=https://www.google.com}) gt 0", + body['expression']) + + # Delete alarm and verify if deleted + resp, body = self.monasca_client.delete_alarm_definition( + alarm_def_id) + self.assertEqual(204, resp.status) + self.assertRaises(exceptions.NotFound, + self.monasca_client.get_alarm_definition, + alarm_def_id) + + # Delete notification + resp, body = self.monasca_client.delete_notification_method( + notification_id) + self.assertEqual(204, resp.status) + + @test.attr(type="gate") + @test.attr(type=['negative']) + def test_create_alarm_definition_with_special_chars_in_expression(self): + notification_name = data_utils.rand_name('notification-') + notification_type = 'EMAIL' + u_address = 'root@localhost' + + resp, body = self.monasca_client.create_notification_method( + notification_name, type=notification_type, address=u_address) + self.assertEqual(201, resp.status) + self.assertEqual(notification_name, body['name']) + notification_id = body['id'] + + # Create an alarm definition + alarm_def_name = data_utils.rand_name('monitoring_alarm') + alarm_definition = helpers.create_alarm_definition( + name=alarm_def_name, + expression="avg(mem_total_mb{dev=\usr\local\bin}) " + "gt 0", + alarm_actions=notification_id, + ok_actions=notification_id, + severity="LOW") + self.assertRaises(exceptions.UnprocessableEntity, + self.monasca_client.create_alarm_definitions, + alarm_definition) + + # List + + @test.attr(type="gate") + def test_list_alarm_definitions(self): + resp, response_body = self.monasca_client.list_alarm_definitions() + self.assertEqual(200, resp.status) + + # Test list alarm definition response body + self.assertTrue(isinstance(response_body, dict)) + self.assertTrue(set(['links', 'elements']) == + set(response_body)) + elements = response_body['elements'] + links = response_body['links'] + self.assertTrue(isinstance(links, list)) + link = links[0] + self.assertTrue(set(['rel', 'href']) == + set(link)) + self.assertEqual(link['rel'], u'self') + self.assertEqual(len(elements), NUM_ALARM_DEFINITIONS) + for definition in elements: + self.assertTrue(set(['id', + 'links', + 'name', + 'description', + 'expression', + 'match_by', + 'severity', + 'actions_enabled', + 'ok_actions', + 'alarm_actions', + 'undetermined_actions']) == + set(definition)) + + @test.attr(type="gate") + def test_list_alarm_definitions_with_name(self): + query_parms = '?name=name-1' + resp, response_body = self.monasca_client.list_alarm_definitions( + query_parms) + self.assertEqual(200, resp.status) + + @test.attr(type="gate") + def test_list_alarm_definitions_with_dimensions(self): + query_parms = '?dimensions=key1:value1' + resp, response_body = self.monasca_client.list_alarm_definitions( + query_parms) + self.assertEqual(200, resp.status) + + @test.attr(type="gate") + def test_list_alarm_definitions_with_offset_limit(self): + query_parms = '?offset=1&limit=2' + resp, response_body = self.monasca_client.list_alarm_definitions( + query_parms) + self.assertEqual(200, resp.status) + + # Get + + @test.attr(type="gate") + def test_get_alarm_definition(self): + alarm_definition = self.create_alarm_definition() + resp, response_body = self.monasca_client.create_alarm_definitions( + alarm_definition) + alarm_def_id = response_body['id'] + resp, response_body = self.monasca_client.get_alarm_definition( + alarm_def_id) + self.assertEqual(200, resp.status) + + # Test Get Alarm Definition Response Body + self.assertTrue(isinstance(response_body, dict)) + self.assertTrue(set(['id', 'links', 'name', 'description', + 'expression', 'match_by', 'severity', + 'actions_enabled', 'alarm_actions', + 'ok_actions', 'undetermined_actions' + ]) == + set(response_body)) + links = response_body['links'] + self.assertTrue(isinstance(links, list)) + link = links[0] + self.assertTrue(set(['rel', 'href']) == + set(link)) + self.assertEqual(link['rel'], u'self') + + # Delete alarm and verify if deleted + resp, response_body = self.monasca_client.delete_alarm_definition( + alarm_def_id) + self.assertEqual(204, resp.status) + self.assertRaises(exceptions.NotFound, + self.monasca_client.get_alarm_definition, + alarm_def_id) + + # Update + + @test.attr(type="gate") + def test_update_alarm_definition(self): + alarm_definition = self.create_alarm_definition() + resp, response_body = self.monasca_client.create_alarm_definitions( + alarm_definition) + id = response_body['id'] + + # Update alarm definition + updated_name = data_utils.rand_name('updated_name') + updated_description = 'updated description' + updated_expression = response_body['expression'] + resp, response_body = self.monasca_client.update_alarm_definition( + id=id, + name=updated_name, + expression=updated_expression, + description=updated_description, + actions_enabled='true' + ) + self.assertEqual(200, resp.status) + + # Validate fields updated + self.assertEqual(updated_name, response_body['name']) + self.assertEqual(updated_expression, response_body['expression']) + self.assertEqual(updated_description, response_body['description']) + + # Get and validate details of alarm definition after update + resp, response_body = self.monasca_client.get_alarm_definition(id) + self.assertEqual(200, resp.status) + self.assertEqual(updated_name, response_body['name']) + self.assertEqual(updated_description, response_body['description']) + self.assertEqual(updated_expression, response_body['expression']) + + # Test Updated alarm definition Response Body + self.assertTrue(isinstance(response_body, dict)) + self.assertTrue(set(['id', 'links', 'name', 'description', + 'expression', 'match_by', 'severity', + 'actions_enabled', 'alarm_actions', + 'ok_actions', 'undetermined_actions' + ]) == + set(response_body)) + + links = response_body['links'] + self.assertTrue(isinstance(links, list)) + link = links[0] + self.assertTrue(set(['rel', 'href']) == + set(link)) + self.assertEqual(link['rel'], u'self') + + # Delete alarm definition + resp, response_body = self.monasca_client.delete_alarm_definition( + id) + self.assertEqual(204, resp.status) + + # Validate alarm ID is not found + self.assertRaises(exceptions.NotFound, + self.monasca_client.get_alarm_definition, id) + + @test.attr(type="gate") + def test_update_notification_in_alarm_definition(self): + notification_name = data_utils.rand_name('notification-') + notification_type = 'EMAIL' + u_address = 'root@localhost' + + resp, body = self.monasca_client.create_notification_method( + notification_name, type=notification_type, address=u_address) + self.assertEqual(201, resp.status) + self.assertEqual(notification_name, body['name']) + notification_id = body['id'] + + # Create an alarm definition + alarm_definition = self.create_alarm_definition() + resp, response_body = self.monasca_client.create_alarm_definitions( + alarm_definition) + self.assertEqual(201, resp.status) + alarm_def_id = response_body['id'] + expression = response_body['expression'] + + # Update alarm definition + alarm_def_name = data_utils.rand_name('monitoring_alarm_update') + resp, body = self.monasca_client.update_alarm_definition( + alarm_def_id, + name=alarm_def_name, + expression=expression, + actions_enabled='true', + alarm_actions=notification_id, + ok_actions=notification_id + ) + self.assertEqual(200, resp.status) + self.assertEqual(alarm_def_name, body['name']) + self.assertEqual(expression, body['expression']) + self.assertEqual(notification_id, body['alarm_actions'][0]) + self.assertEqual(notification_id, body['ok_actions'][0]) + + # Get and verify details of an alarm after update + resp, body = self.monasca_client.get_alarm_definition(alarm_def_id) + self.assertEqual(200, resp.status) + self.assertEqual(alarm_def_name, body['name']) + self.assertEqual(expression, body['expression']) + + # Delete alarm and verify if deleted + resp, _ = self.monasca_client.delete_alarm_definition(alarm_def_id) + self.assertEqual(204, resp.status) + self.assertRaises(exceptions.NotFound, + self.monasca_client.get_alarm_definition, + alarm_def_id) + + # Delete notification + resp, body = self.monasca_client.delete_notification_method( + notification_id) + self.assertEqual(204, resp.status) + + # Patch + + @test.attr(type="gate") + def test_patch_alarm_definition(self): + alarm_definition = self.create_alarm_definition() + resp, response_body = self.monasca_client.create_alarm_definitions( + alarm_definition) + id = response_body['id'] + + # Patch alarm definition + patched_name = data_utils.rand_name('patched_name') + resp, response_body = self.monasca_client.patch_alarm_definition( + id=id, + name=patched_name + ) + self.assertEqual(200, resp.status) + + # Validate fields updated + self.assertEqual(patched_name, response_body['name']) + + # Delete alarm definition + resp, response_body = self.monasca_client.delete_alarm_definition( + id) + self.assertEqual(204, resp.status) + + # Validate alarm ID is not found + self.assertRaises(exceptions.NotFound, + self.monasca_client.get_alarm_definition, + id) + + # Delete + @test.attr(type="gate") + def test_create_and_delete_alarm_definition(self): + alarm_definition = self.create_alarm_definition() + resp, response_body = self.monasca_client.create_alarm_definitions( + alarm_definition) + alarm_def_id = response_body['id'] + + # Delete alarm and verify if deleted + resp, response_body = self.monasca_client.delete_alarm_definition( + alarm_def_id) + self.assertEqual(204, resp.status) + self.assertRaises(exceptions.NotFound, + self.monasca_client.get_alarm_definition, + alarm_def_id) diff --git a/monasca_tempest_tests/tests/api/test_alarms.py b/monasca_tempest_tests/tests/api/test_alarms.py new file mode 100644 index 000000000..bf8760aca --- /dev/null +++ b/monasca_tempest_tests/tests/api/test_alarms.py @@ -0,0 +1,281 @@ +# (C) Copyright 2015 Hewlett Packard Enterprise Development Company LP +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +# TODO(RMH): Update documentation. Get alarms returns alarm_definition, not +# TODO(RMH): alarm_definition_id in response body +import time + +from monasca_tempest_tests.tests.api import base +from monasca_tempest_tests.tests.api import helpers +from tempest.common.utils import data_utils +from tempest import test +from tempest_lib import exceptions + + +class TestAlarms(base.BaseMonascaTest): + + @classmethod + def resource_setup(cls): + super(TestAlarms, cls).resource_setup() + + @classmethod + def resource_cleanup(cls): + super(TestAlarms, cls).resource_cleanup() + + @classmethod + def create_alarms_for_test_alarms(cls): + # create an alarm definition + expression = "avg(name-1) > 0" + name = data_utils.rand_name('name-1') + alarm_definition = helpers.create_alarm_definition( + name=name, expression=expression) + resp, response_body = cls.monasca_client.create_alarm_definitions( + alarm_definition) + # create some metrics + for i in xrange(30): + metric = helpers.create_metric() + resp, response_body = cls.monasca_client.create_metrics(metric) + time.sleep(1) + resp, response_body = cls.monasca_client.list_alarms() + elements = response_body['elements'] + if len(elements) > 0: + break + + @test.attr(type="gate") + def test_list_alarms(self): + self.create_alarms_for_test_alarms() + resp, response_body = self.monasca_client.list_alarms() + self.assertEqual(200, resp.status) + self.assertTrue(set(['links', 'elements']) == + set(response_body)) + elements = response_body['elements'] + element = elements[0] + self.assertTrue(set(['id', + 'links', + 'alarm_definition', + 'metrics', + 'state', + 'lifecycle_state', + 'link', + 'state_updated_timestamp', + 'updated_timestamp', + 'created_timestamp']) == + set(element)) + + for metric in element['metrics']: + target_metric = helpers.create_metric() + self.assertEqual(target_metric['name'], metric['name']) + self.assertEqual(target_metric['dimensions'], metric['dimensions']) + + @test.attr(type="gate") + def test_list_alarms_by_alarm_definition_id(self): + resp, response_body = self.monasca_client.list_alarms() + elements = response_body['elements'] + element = elements[0] + alarm_definition_id = element['alarm_definition']['id'] + query_parms = '?alarm_definition_id=' + str(alarm_definition_id) + resp, response_body = self.monasca_client.list_alarms(query_parms) + self.assertEqual(200, resp.status) + element_1 = response_body['elements'][0] + self.assertEqual(element_1, element) + + @test.attr(type="gate") + def test_list_alarms_by_metric_name(self): + resp, response_body = self.monasca_client.list_alarms() + elements = response_body['elements'] + element = elements[0] + metric_name = element['metrics'][0]['name'] + query_parms = '?metric_name=' + str(metric_name) + resp, response_body = self.monasca_client.list_alarms(query_parms) + self.assertEqual(200, resp.status) + element_1 = response_body['elements'][0] + self.assertEqual(element_1, element) + + @test.attr(type="gate") + def test_list_alarms_by_metric_dimensions(self): + query_parms = '?metric_dimensions=key-2:value-2,key-1:value-1' + resp, response_body_1 = self.monasca_client.list_alarms(query_parms) + self.assertEqual(200, resp.status) + + @test.attr(type="gate") + def test_list_alarms_by_state(self): + resp, response_body = self.monasca_client.list_alarms() + len0 = len(response_body['elements']) + query_parms = '?state=UNDETERMINED' + resp, response_body1 = self.monasca_client.list_alarms(query_parms) + len1 = len(response_body1['elements']) + self.assertEqual(200, resp.status) + + query_parms = '?state=OK' + resp, response_body2 = self.monasca_client.list_alarms(query_parms) + len2 = len(response_body2['elements']) + self.assertEqual(200, resp.status) + + query_parms = '?state=ALARM' + resp, response_body3 = self.monasca_client.list_alarms(query_parms) + len3 = len(response_body3['elements']) + self.assertEqual(200, resp.status) + + self.assertEqual(len0, len1 + len2 + len3) + + @test.attr(type="gate") + def test_list_alarms_by_lifecycle_state(self): + query_parms = '?lifecycle_state=None' + resp, response_body = self.monasca_client.list_alarms(query_parms) + self.assertEqual(200, resp.status) + + @test.attr(type="gate") + def test_list_alarms_by_link(self): + query_parms = '?link=None' + resp, response_body = self.monasca_client.list_alarms(query_parms) + self.assertEqual(200, resp.status) + + @test.attr(type="gate") + def test_list_alarms_by_state_updated_start_time(self): + resp, response_body = self.monasca_client.list_alarms() + elements = response_body['elements'] + element = elements[0] + state_updated_start_time = element['state_updated_timestamp'] + query_parms = '?state_updated_timestamp=' + \ + str(state_updated_start_time) + resp, response_body = self.monasca_client.list_alarms(query_parms) + self.assertEqual(200, resp.status) + element_1 = response_body['elements'][0] + self.assertEqual(element, element_1) + + @test.attr(type="gate") + def test_list_alarms_by_offset_limit(self): + resp, response_body = self.monasca_client.list_alarms() + elements = response_body['elements'] + first_element = elements[0] + next_element = elements[1] + id = first_element['id'] + query_parms = '?offset=' + str(id) + '&limit=1' + resp, response_body1 = self.monasca_client.list_alarms(query_parms) + elements = response_body1['elements'] + self.assertEqual(1, len(elements)) + self.assertEqual(elements[0]['id'], next_element['id']) + self.assertEqual(elements[0], next_element) + + @test.attr(type="gate") + def test_get_alarm(self): + self.create_alarms_for_test_alarms() + resp, response_body = self.monasca_client.list_alarms() + self.assertEqual(200, resp.status) + elements = response_body['elements'] + element = elements[0] + id = element['id'] + resp, response_body = self.monasca_client.get_alarm(id) + self.assertEqual(200, resp.status) + self.assertTrue(set(['id', + 'links', + 'alarm_definition', + 'metrics', + 'state', + 'lifecycle_state', + 'link', + 'state_updated_timestamp', + 'updated_timestamp', + 'created_timestamp']) == + set(response_body)) + for metric in element['metrics']: + target_metric = helpers.create_metric() + self.assertEqual(target_metric['name'], metric['name']) + self.assertEqual(target_metric['dimensions'], metric['dimensions']) + + @test.attr(type="gate") + @test.attr(type=['negative']) + def test_get_alarm_with_invalid_id(self): + id = data_utils.rand_name() + self.assertRaises(exceptions.NotFound, self.monasca_client.get_alarm, + id) + + @test.attr(type="gate") + def test_update_alarm(self): + self.create_alarms_for_test_alarms() + resp, response_body = self.monasca_client.list_alarms() + self.assertEqual(200, resp.status) + elements = response_body['elements'] + element = elements[0] + id = element['id'] + updated_state = "ALARM" + updated_lifecycle_state = "OPEN" + updated_link = "http://somesite.com" + resp, response_body = self.monasca_client.update_alarm( + id=id, state=updated_state, + lifecycle_state=updated_lifecycle_state, link=updated_link) + self.assertEqual(200, resp.status) + self.assertTrue(set(['id', + 'links', + 'alarm_definition', + 'metrics', + 'state', + 'lifecycle_state', + 'link', + 'state_updated_timestamp', + 'updated_timestamp', + 'created_timestamp']) == + set(response_body)) + + # Validate fields updated + self.assertEqual(updated_state, response_body['state']) + self.assertEqual(updated_lifecycle_state, response_body[ + 'lifecycle_state']) + self.assertEqual(updated_link, response_body[ + 'link']) + + @test.attr(type="gate") + def test_patch_alarm(self): + self.create_alarms_for_test_alarms() + resp, response_body = self.monasca_client.list_alarms() + self.assertEqual(200, resp.status) + elements = response_body['elements'] + element = elements[0] + id = element['id'] + updated_state = "UNDETERMINED" + resp, response_body = self.monasca_client.patch_alarm( + id=id, state=updated_state) + self.assertEqual(200, resp.status) + self.assertTrue(set(['id', + 'links', + 'alarm_definition', + 'metrics', + 'state', + 'lifecycle_state', + 'link', + 'state_updated_timestamp', + 'updated_timestamp', + 'created_timestamp']) == + set(response_body)) + + # Validate the field patched + self.assertEqual(updated_state, response_body['state']) + + @test.attr(type="gate") + def test_delete_alarm(self): + self.create_alarms_for_test_alarms() + resp, response_body = self.monasca_client.list_alarms() + self.assertEqual(200, resp.status) + elements = response_body['elements'] + element = elements[0] + id = element['id'] + resp, response_body = self.monasca_client.delete_alarm(id) + self.assertEqual(204, resp.status) + + @test.attr(type="gate") + @test.attr(type=['negative']) + def test_delete_alarm_with_invalid_id(self): + id = data_utils.rand_name() + self.assertRaises(exceptions.NotFound, + self.monasca_client.delete_alarm, id) diff --git a/monasca_tempest_tests/tests/api/test_alarms_state_history.py b/monasca_tempest_tests/tests/api/test_alarms_state_history.py new file mode 100644 index 000000000..0eae8b5de --- /dev/null +++ b/monasca_tempest_tests/tests/api/test_alarms_state_history.py @@ -0,0 +1,212 @@ +# (C) Copyright 2015 Hewlett Packard Enterprise Development Company LP +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +# TODO(RMH): Update documentation. Get alarms returns alarm_definition, not +# TODO(RMH): alarm_definition_id in response body +import time + +from monasca_tempest_tests.tests.api import base +from monasca_tempest_tests.tests.api import helpers +from oslo_utils import timeutils +from tempest.common.utils import data_utils +from tempest import test + + +class TestAlarmsStateHistory(base.BaseMonascaTest): + + @classmethod + def resource_setup(cls): + super(TestAlarmsStateHistory, cls).resource_setup() + + start_timestamp = int(time.time() * 1000) + end_timestamp = int(time.time() * 1000) + 1000 + + # create an alarm definition + expression = "avg(name-1) > 0" + name = data_utils.rand_name('alarm_definition') + alarm_definition = helpers.create_alarm_definition( + name=name, + expression=expression) + resp, response_body = cls.monasca_client.create_alarm_definitions( + alarm_definition) + + # create another alarm definition + name1 = data_utils.rand_name('alarm_definition1') + expression1 = "max(cpu.system_perc) > 0" + alarm_definition1 = helpers.create_alarm_definition( + name=name1, + expression=expression1) + resp, response_body1 = cls.monasca_client.create_alarm_definitions( + alarm_definition1) + + # create another alarm definition + name2 = data_utils.rand_name('alarm_definition2') + expression1 = "avg(mysql.performance.slow_queries) > 10.0" + alarm_definition2 = helpers.create_alarm_definition( + name=name2, + expression=expression1) + resp, response_body2 = cls.monasca_client.create_alarm_definitions( + alarm_definition2) + + # create some metrics + for i in xrange(180): + metric = helpers.create_metric() + resp, body = cls.monasca_client.create_metrics(metric) + cls._start_timestamp = start_timestamp + i + cls._end_timestamp = end_timestamp + i + time.sleep(1) + resp, response_body = cls.monasca_client.\ + list_alarms_state_history() + elements = response_body['elements'] + if len(elements) > 4: + break + + if len(elements) < 3: + cls.assertEqual(1, False) + + @test.attr(type="gate") + def test_list_alarms_state_history(self): + resp, response_body = self.monasca_client.list_alarms_state_history() + self.assertEqual(200, resp.status) + # Test response body + self.assertTrue(set(['links', 'elements']) == set(response_body)) + elements = response_body['elements'] + element = elements[0] + self.assertTrue(set(['id', 'alarm_id', 'metrics', 'old_state', + 'new_state', 'reason', 'reason_data', 'timestamp', + 'sub_alarms']) == set(element)) + + @test.attr(type="gate") + def test_list_alarms_state_history_with_dimensions(self): + resp, response_body = self.monasca_client.list_alarms_state_history() + element = response_body['elements'][0] + dimension = element['metrics'][0]['dimensions'] + dimension_items = dimension.items() + dimension_item = dimension_items[0] + dimension_item_0 = dimension_item[0] + dimension_item_1 = dimension_item[1] + name = element['metrics'][0]['name'] + + query_parms = '?dimensions=' + str(dimension_item_0) + ':' + str( + dimension_item_1) + resp, response_body = self.monasca_client.list_alarms_state_history( + query_parms) + name_new = response_body['elements'][0]['metrics'][0]['name'] + self.assertEqual(200, resp.status) + self.assertEqual(name, name_new) + + @test.attr(type="gate") + def test_list_alarms_state_history_with_start_time(self): + current_time = int(time.time()) + current_time = timeutils.iso8601_from_timestamp(current_time) + query_parms = '?start_time=' + str(current_time) + resp, response_body = self.monasca_client.list_alarms_state_history( + query_parms) + elements = response_body['elements'] + self.assertEqual(0, len(elements)) + + resp, response_body = self.monasca_client.list_alarms_state_history() + elements = response_body['elements'] + timestamp = elements[1]['timestamp'] + query_parms = '?start_time=' + str(timestamp) + resp, response_body = self.monasca_client.list_alarms_state_history( + query_parms) + elements = response_body['elements'] + self.assertEqual(2, len(elements)) + + @test.attr(type="gate") + def test_list_alarms_state_history_with_end_time(self): + resp, response_body = self.monasca_client.list_alarms_state_history() + elements = response_body['elements'] + timestamp = elements[2]['timestamp'] + query_parms = '?end_time=' + str(timestamp) + resp, response_body = self.monasca_client.list_alarms_state_history( + query_parms) + elements = response_body['elements'] + self.assertEqual(1, len(elements)) + + @test.attr(type="gate") + def test_list_alarms_state_history_with_offset_limit(self): + resp, response_body = self.monasca_client.list_alarms_state_history() + elements = response_body['elements'] + first_element = elements[0] + last_element = elements[2] + first_element_id = first_element['id'] + last_element_id = last_element['id'] + + for limit in xrange(1, 4): + query_parms = '?limit=' + str(limit) + '&offset=' + str( + last_element_id) + resp, response_body = self.monasca_client.\ + list_alarms_state_history(query_parms) + elements = response_body['elements'] + element_new = elements[0] + self.assertEqual(200, resp.status) + self.assertEqual(element_new, first_element) + self.assertEqual(limit, len(elements)) + id_new = element_new['id'] + self.assertEqual(id_new, first_element_id) + + @test.attr(type="gate") + def test_list_alarm_state_history(self): + # Get the alarm state history for a specific alarm by ID + resp, response_body = self.monasca_client.list_alarms_state_history() + self.assertEqual(200, resp.status) + elements = response_body['elements'] + element = elements[0] + alarm_id = element['alarm_id'] + resp, response_body = self.monasca_client.list_alarm_state_history( + alarm_id) + self.assertEqual(200, resp.status) + + # Test Response Body + self.assertTrue(set(['links', 'elements']) == + set(response_body)) + elements = response_body['elements'] + links = response_body['links'] + self.assertTrue(isinstance(links, list)) + link = links[0] + self.assertTrue(set(['rel', 'href']) == + set(link)) + self.assertEqual(link['rel'], u'self') + definition = elements[0] + self.assertTrue(set(['id', 'alarm_id', 'metrics', 'new_state', + 'old_state', 'reason', 'reason_data', + 'sub_alarms', 'timestamp']) == + set(definition)) + + @test.attr(type="gate") + def test_list_alarm_state_history_with_offset_limit(self): + # Get the alarm state history for a specific alarm by ID + resp, response_body = self.monasca_client.list_alarms_state_history() + self.assertEqual(200, resp.status) + elements = response_body['elements'] + element = elements[0] + + alarm_id = element['alarm_id'] + query_parms = '?limit=1' + resp, response_body = self.monasca_client.list_alarm_state_history( + alarm_id, query_parms) + elements = response_body['elements'] + self.assertEqual(200, resp.status) + self.assertEqual(1, len(elements)) + + id = element['id'] + query_parms = '?limit=1&offset=' + str(id) + resp, response_body = self.monasca_client.list_alarm_state_history( + alarm_id, query_parms) + elements_new = response_body['elements'] + self.assertEqual(200, resp.status) + self.assertEqual(1, len(elements_new)) + self.assertEqual(element, elements_new[0]) diff --git a/monasca_tempest_tests/tests/api/test_measurements.py b/monasca_tempest_tests/tests/api/test_measurements.py new file mode 100644 index 000000000..80c81b0c1 --- /dev/null +++ b/monasca_tempest_tests/tests/api/test_measurements.py @@ -0,0 +1,221 @@ +# (C) Copyright 2015 Hewlett Packard Enterprise Development Company LP +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import time + +from oslo_utils import timeutils + +from monasca_tempest_tests.tests.api import base +from monasca_tempest_tests.tests.api import constants +from monasca_tempest_tests.tests.api import helpers +from tempest.common.utils import data_utils +from tempest import test +from tempest_lib import exceptions + +NUM_MEASUREMENTS = 100 +WAIT_TIME = 30 + + +class TestMeasurements(base.BaseMonascaTest): + + @classmethod + def resource_setup(cls): + super(TestMeasurements, cls).resource_setup() + + start_timestamp = int(time.time() * 1000) + end_timestamp = int(time.time() * 1000) + NUM_MEASUREMENTS * 1000 + metrics = [] + + for i in xrange(NUM_MEASUREMENTS): + metric = helpers.create_metric( + name="name-1", + timestamp=start_timestamp + i) + metrics.append(metric) + + resp, response_body = cls.monasca_client.create_metrics(metrics) + cls._start_timestamp = start_timestamp + cls._end_timestamp = end_timestamp + cls._metrics = metrics + + @test.attr(type="gate") + def test_list_measurements(self): + start_time = timeutils.iso8601_from_timestamp( + self._start_timestamp / 1000) + query_parms = '?name=name-1&merge_metrics=true&start_time=' + str( + start_time) + resp, response_body = self.monasca_client.list_measurements( + query_parms) + self.assertEqual(200, resp.status) + + self.assertTrue(set(['links', 'elements']) == set(response_body)) + elements = response_body['elements'] + element = elements[0] + self.assertTrue(set(['id', 'name', 'dimensions', 'columns', + 'measurements']) == set(element)) + self.assertTrue(type(element['name']) is unicode) + self.assertTrue(type(element['dimensions']) is dict) + self.assertTrue(type(element['columns']) is list) + self.assertTrue(type(element['measurements']) is list) + + @test.attr(type="gate") + @test.attr(type=['negative']) + def test_list_measurements_with_no_start_time(self): + query_parms = '?name=name-1' + self.assertRaises(exceptions.UnprocessableEntity, + self.monasca_client.list_measurements, query_parms) + + @test.attr(type="gate") + @test.attr(type=['negative']) + def test_list_measurements_with_no_name(self): + start_time = timeutils.iso8601_from_timestamp( + self._start_timestamp / 1000) + query_parms = '?start_time=' + str(start_time) + self.assertRaises(exceptions.UnprocessableEntity, + self.monasca_client.list_measurements, query_parms) + + @test.attr(type="gate") + def test_list_measurements_with_dimensions(self): + key1 = data_utils.rand_name('key1') + value1 = data_utils.rand_name('value1') + start_timestamp = int(time.time() * 1000) + name = data_utils.rand_name() + metric = [ + helpers.create_metric(name=name, timestamp=start_timestamp, + dimensions={key1: value1}, value=123) + ] + resp, response_body = self.monasca_client.create_metrics(metric) + time.sleep(WAIT_TIME) + + start_time = timeutils.iso8601_from_timestamp( + self._start_timestamp / 1000) + query_parms = '?name=' + name + '&start_time=' + str( + start_time) + '&dimensions=' + key1 + ':' + value1 + resp, response_body = self.monasca_client.list_measurements( + query_parms) + value_new = response_body['elements'][0]['measurements'][0][1] + self.assertEqual(200, resp.status) + self.assertEqual(123, value_new) + + @test.attr(type="gate") + def test_list_measurements_with_endtime(self): + start_time = timeutils.iso8601_from_timestamp( + self._start_timestamp / 1000) + end_time = timeutils.iso8601_from_timestamp( + self._end_timestamp / 1000) + query_parms = '?name=name-1&merge_metrics=true&true&start_time=' + str( + start_time) + '&end_time' + str(end_time) + resp, body = self.monasca_client.list_measurements(query_parms) + self.assertEqual(200, resp.status) + len_measurements = len(body['elements'][0]['measurements']) + self.assertEqual(len_measurements, NUM_MEASUREMENTS) + + @test.attr(type="gate") + def test_list_measurements_with_offset_limit(self): + start_timestamp = int(time.time() * 1000) + name = data_utils.rand_name() + metric = [ + helpers.create_metric(name=name, timestamp=start_timestamp + 0, + dimensions={'key1': 'value-1', + 'key2': 'value-1'}), + helpers.create_metric(name=name, timestamp=start_timestamp + 1, + dimensions={'key1': 'value-2', + 'key2': 'value-2'}), + helpers.create_metric(name=name, timestamp=start_timestamp + 2, + dimensions={'key1': 'value-3', + 'key2': 'value-3'}), + helpers.create_metric(name=name, timestamp=start_timestamp + 3, + dimensions={'key1': 'value-4', + 'key2': 'value-4'}) + ] + + resp, response_body = self.monasca_client.create_metrics(metric) + time.sleep(WAIT_TIME) + + query_parms = '?name=' + name + resp, response_body = self.monasca_client.list_metrics(query_parms) + self.assertEqual(200, resp.status) + + start_time = timeutils.iso8601_from_timestamp( + start_timestamp / 1000) + query_parms = '?name=' + name + '&merge_metrics=true&start_time=' + \ + str(start_time) + resp, body = self.monasca_client.list_measurements(query_parms) + self.assertEqual(200, resp.status) + elements = body['elements'][0]['measurements'] + first_element = elements[0] + last_element = elements[3] + + query_parms = '?name=' + name + '&merge_metrics=true&start_time=' + \ + str(start_time) + '&limit=4' + resp, response_body = self.monasca_client.list_measurements( + query_parms) + self.assertEqual(200, resp.status) + + elements = response_body['elements'][0]['measurements'] + self.assertEqual(4, len(elements)) + + self.assertEqual(first_element, elements[0]) + + for limit in xrange(1, 5): + next_element = elements[limit - 1] + while True: + query_parms = '?name=' + name + \ + '&merge_metrics=true&start_time=' + \ + str(start_time) + '&offset=' + \ + str(next_element[0]) + '&limit=' + \ + str(limit) + resp, response_body = self.monasca_client.list_measurements( + query_parms) + self.assertEqual(200, resp.status) + new_elements = response_body['elements'][0]['measurements'] + + if len(new_elements) > limit - 1: + self.assertEqual(limit, len(new_elements)) + next_element = new_elements[limit - 1] + elif len(new_elements) > 0 and len(new_elements) <= limit - 1: + self.assertEqual(last_element, new_elements[0]) + break + else: + self.assertEqual(last_element, next_element) + break + + @test.attr(type="gate") + def test_list_measurements_with_merge_metrics(self): + start_time = timeutils.iso8601_from_timestamp( + self._start_timestamp / 1000) + query_parms = '?name=name-1&merge_metrics=true&start_time=' + str( + start_time) + resp, response_body = self.monasca_client.list_measurements( + query_parms) + self.assertEqual(200, resp.status) + + @test.attr(type="gate") + def test_list_measurements_with_name_exceeds_max_length(self): + long_name = "x" * (constants.MAX_LIST_MEASUREMENTS_NAME_LENGTH + 1) + start_time = timeutils.iso8601_from_timestamp(self._start_timestamp + / 1000) + query_parms = '?name=' + str(long_name) \ + + '&merge_metrics=true&start_time=' + str(start_time) + self.assertRaises(exceptions.UnprocessableEntity, + self.monasca_client.list_measurements, query_parms) + + @test.attr(type="gate") + @test.attr(type=['negative']) + def test_list_measurements_with_no_merge_metrics(self): + start_time = timeutils.iso8601_from_timestamp( + self._start_timestamp / 1000) + query_parms = '?name=name-1&merge_metrics=false&start_time=' + str( + start_time) + self.assertRaises(exceptions.Conflict, + self.monasca_client.list_measurements, query_parms) diff --git a/monasca_tempest_tests/tests/api/test_metrics.py b/monasca_tempest_tests/tests/api/test_metrics.py new file mode 100644 index 000000000..ccabcb796 --- /dev/null +++ b/monasca_tempest_tests/tests/api/test_metrics.py @@ -0,0 +1,236 @@ +# (C) Copyright 2015 Hewlett Packard Enterprise Development Company LP +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +# TODO(RMH): Check if ' should be added in the list of INVALID_CHARS. +# TODO(RMH): test_create_metric_no_value, should return 422 if value not sent + +import time + +from monasca_tempest_tests.tests.api import base +from monasca_tempest_tests.tests.api import constants +from monasca_tempest_tests.tests.api import helpers +from tempest.common.utils import data_utils +from tempest import test +from tempest_lib import exceptions + +WAIT_TIME = 30 + + +class TestMetrics(base.BaseMonascaTest): + + @classmethod + def resource_setup(cls): + super(TestMetrics, cls).resource_setup() + + @test.attr(type='gate') + def test_create_metric(self): + metric = helpers.create_metric() + resp, body = self.monasca_client.create_metrics(metric) + self.assertEqual(204, resp.status) + + @test.attr(type='gate') + def test_create_metrics(self): + metrics = [ + helpers.create_metric(), + helpers.create_metric() + ] + resp, body = self.monasca_client.create_metrics(metrics) + self.assertEqual(204, resp.status) + + @test.attr(type='gate') + @test.attr(type=['negative']) + def test_create_metric_with_no_name(self): + metric = helpers.create_metric(name=None) + self.assertRaises(exceptions.UnprocessableEntity, + self.monasca_client.create_metrics, + metric) + + @test.attr(type='gate') + def test_create_metric_with_no_dimensions(self): + metric = helpers.create_metric(dimensions=None) + resp, body = self.monasca_client.create_metrics(metric) + self.assertEqual(204, resp.status) + + @test.attr(type='gate') + @test.attr(type=['negative']) + def test_create_metric_with_no_timestamp(self): + metric = helpers.create_metric(timestamp=None) + self.assertRaises(exceptions.UnprocessableEntity, + self.monasca_client.create_metrics, + metric) + + @test.attr(type='gate') + @test.attr(type=['negative']) + def test_create_metric_no_value(self): + timestamp = time.time() * 1000 + metric = helpers.create_metric(timestamp=timestamp, + value=None) + return + self.assertRaises(exceptions.UnprocessableEntity, + self.monasca_client.create_metrics, + metric) + + @test.attr(type='gate') + @test.attr(type=['negative']) + def test_create_metric_with_name_exceeds_max_length(self): + long_name = "x" * (constants.MAX_METRIC_NAME_LENGTH + 1) + metric = helpers.create_metric(long_name) + self.assertRaises(exceptions.UnprocessableEntity, + self.monasca_client.create_metrics, + metric) + + @test.attr(type='gate') + @test.attr(type=['negative']) + def test_create_metric_with_invalid_chars_in_name(self): + for invalid_char in constants.INVALID_CHARS: + metric = helpers.create_metric(invalid_char) + self.assertRaises(exceptions.UnprocessableEntity, + self.monasca_client.create_metrics, + metric) + + @test.attr(type='gate') + @test.attr(type=['negative']) + def test_create_metric_with_invalid_chars_in_dimensions(self): + for invalid_char in constants.INVALID_CHARS: + metric = helpers.create_metric('name-1', {'key-1': invalid_char}) + self.assertRaises(exceptions.UnprocessableEntity, + self.monasca_client.create_metrics, + metric) + for invalid_char in constants.INVALID_CHARS: + metric = helpers.create_metric('name-1', {invalid_char: 'value-1'}) + self.assertRaises(exceptions.UnprocessableEntity, + self.monasca_client.create_metrics, + metric) + + @test.attr(type='gate') + @test.attr(type=['negative']) + def test_create_metric_dimension_key_exceeds_max_length(self): + long_key = "x" * (constants.MAX_DIMENSION_KEY_LENGTH + 1) + metric = helpers.create_metric('name-1', {long_key: 'value-1'}) + self.assertRaises(exceptions.UnprocessableEntity, + self.monasca_client.create_metrics, + metric) + + @test.attr(type='gate') + @test.attr(type=['negative']) + def test_create_metric_dimension_value_exceeds_max_length(self): + long_value = "x" * (constants.MAX_DIMENSION_VALUE_LENGTH + 1) + metric = helpers.create_metric('name-1', {'key-1': long_value}) + self.assertRaises(exceptions.UnprocessableEntity, + self.monasca_client.create_metrics, + metric) + + @test.attr(type='gate') + def test_list_metrics(self): + resp, response_body = self.monasca_client.list_metrics() + self.assertEqual(200, resp.status) + self.assertTrue(set(['links', 'elements']) == set(response_body)) + elements = response_body['elements'] + element = elements[0] + self.assertTrue(set(['id', 'name', 'dimensions']) == set(element)) + self.assertTrue(type(element['id']) is unicode) + self.assertTrue(type(element['name']) is unicode) + self.assertTrue(type(element['dimensions']) is dict) + + @test.attr(type='gate') + def test_list_metrics_with_name(self): + name_org = data_utils.rand_name('name') + key = data_utils.rand_name('key') + metric = helpers.create_metric(name=name_org, + dimensions={key: 'value-1'}) + resp, body = self.monasca_client.create_metrics(metric) + time.sleep(WAIT_TIME) + + query_parms = '?dimensions=' + str(key) + ':value-1' + resp, response_body = self.monasca_client.list_metrics(query_parms) + self.assertEqual(200, resp.status) + elements = response_body['elements'] + dimensions = elements[0] + name = dimensions['name'] + self.assertEqual(name_org, str(name)) + + @test.attr(type='gate') + def test_list_metrics_with_dimensions(self): + name = data_utils.rand_name('name') + key = data_utils.rand_name('key') + value_org = data_utils.rand_name('value') + metric = helpers.create_metric(name=name, + dimensions={key: value_org}) + resp, body = self.monasca_client.create_metrics(metric) + time.sleep(WAIT_TIME) + + query_parms = '?name=' + name + resp, response_body = self.monasca_client.list_metrics(query_parms) + self.assertEqual(200, resp.status) + elements = response_body['elements'] + dimensions = elements[0] + dimension = dimensions['dimensions'] + value = dimension[unicode(key)] + self.assertEqual(value_org, str(value)) + + @test.attr(type='gate') + def test_list_metrics_with_offset_limit(self): + name = data_utils.rand_name() + key1 = data_utils.rand_name() + key2 = data_utils.rand_name() + + metrics = [ + helpers.create_metric(name=name, dimensions={ + key1: 'value-1', key2: 'value-1'}), + helpers.create_metric(name=name, dimensions={ + key1: 'value-2', key2: 'value-2'}), + helpers.create_metric(name=name, dimensions={ + key1: 'value-3', key2: 'value-3'}), + helpers.create_metric(name=name, dimensions={ + key1: 'value-4', key2: 'value-4'}) + ] + resp, body = self.monasca_client.create_metrics(metrics) + time.sleep(WAIT_TIME) + + query_parms = '?name=' + name + resp, response_body = self.monasca_client.list_metrics(query_parms) + self.assertEqual(200, resp.status) + + elements = response_body['elements'] + first_element = elements[0] + last_element = elements[3] + + query_parms = '?name=' + name + '&limit=4' + resp, response_body = self.monasca_client.list_metrics(query_parms) + self.assertEqual(200, resp.status) + + elements = response_body['elements'] + self.assertEqual(4, len(elements)) + + self.assertEqual(first_element, elements[0]) + + for limit in xrange(1, 5): + next_element = elements[limit - 1] + while True: + query_parms = '?name=' + name + '&offset=' +\ + str(next_element['id']) + '&limit=' + str(limit) + resp, response_body = self.monasca_client.list_metrics( + query_parms) + self.assertEqual(200, resp.status) + new_elements = response_body['elements'] + + if len(new_elements) > limit - 1: + self.assertEqual(limit, len(new_elements)) + next_element = new_elements[limit - 1] + elif len(new_elements) > 0 and len(new_elements) <= limit - 1: + self.assertEqual(last_element, new_elements[0]) + break + else: + self.assertEqual(last_element, next_element) + break diff --git a/monasca_tempest_tests/tests/api/test_metrics_names.py b/monasca_tempest_tests/tests/api/test_metrics_names.py new file mode 100644 index 000000000..48b9679c4 --- /dev/null +++ b/monasca_tempest_tests/tests/api/test_metrics_names.py @@ -0,0 +1,47 @@ +# (C) Copyright 2015 Hewlett Packard Enterprise Development Company LP +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from monasca_tempest_tests.tests.api import base +from tempest import test + + +class TestMetricsNames(base.BaseMonascaTest): + + @classmethod + def resource_setup(cls): + super(TestMetricsNames, cls).resource_setup() + + @test.attr(type='gate') + def test_list_metrics_names(self): + resp, response_body = self.monasca_client.list_metrics_names() + self.assertEqual(200, resp.status) + self.assertTrue(set(['links', 'elements']) == set(response_body)) + elements = response_body['elements'] + element = elements[0] + self.assertTrue(set(['id', 'name']) == set(element)) + + @test.attr(type='gate') + def test_list_metrics_names_with_dimensions(self): + query_parms = '?dimensions=key1:value1' + resp, response_body = self.monasca_client.list_metrics_names( + query_parms) + self.assertEqual(200, resp.status) + + @test.attr(type='gate') + def test_list_metrics_names_with_limit_offset(self): + # Can not test list_metrics_names_with_limit_offset for now because + # list_metrics_names returns a list of metric names with no + # duplicates. But the limit and offset are using the original list + # with duplicates as reference. + return diff --git a/monasca_tempest_tests/tests/api/test_notification_methods.py b/monasca_tempest_tests/tests/api/test_notification_methods.py new file mode 100644 index 000000000..c5a047608 --- /dev/null +++ b/monasca_tempest_tests/tests/api/test_notification_methods.py @@ -0,0 +1,297 @@ +# (C) Copyright 2015 Hewlett Packard Enterprise Development Company LP +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +# TODO(RMH): Validate whether a 200 or 201 should be returned and resolve. +# TODO(RMH): Documentation says 200, but a 201 is being returned. + +from monasca_tempest_tests.tests.api import base +from monasca_tempest_tests.tests.api import constants +from monasca_tempest_tests.tests.api import helpers +from tempest.common.utils import data_utils +from tempest import test +from tempest_lib import exceptions + +DEFAULT_EMAIL_ADDRESS = 'john.doe@domain.com' + + +class TestNotificationMethods(base.BaseMonascaTest): + + @classmethod + def resource_setup(cls): + super(TestNotificationMethods, cls).resource_setup() + + @test.attr(type="gate") + def test_create_notification_method(self): + notification = helpers.create_notification() + resp, response_body = self.monasca_client.create_notifications( + notification) + self.assertEqual(201, resp.status) + id = response_body['id'] + resp, response_body = self.monasca_client.\ + delete_notification_method(id) + self.assertEqual(204, resp.status) + + @test.attr(type="gate") + @test.attr(type=['negative']) + def test_create_notification_method_with_no_name(self): + notification = helpers.create_notification(name=None) + self.assertRaises(exceptions.UnprocessableEntity, + self.monasca_client.create_notifications, + notification) + + @test.attr(type="gate") + @test.attr(type=['negative']) + def test_create_notification_method_with_no_type(self): + notification = helpers.create_notification(type=None) + self.assertRaises(exceptions.UnprocessableEntity, + self.monasca_client.create_notifications, + notification) + + @test.attr(type="gate") + @test.attr(type=['negative']) + def test_create_notification_method_with_no_address(self): + notification = helpers.create_notification(address=None) + self.assertRaises(exceptions.UnprocessableEntity, + self.monasca_client.create_notifications, + notification) + + @test.attr(type="gate") + @test.attr(type=['negative']) + def test_create_notification_method_with_name_exceeds_max_length(self): + long_name = "x" * (constants.MAX_NOTIFICATION_METHOD_NAME_LENGTH + 1) + notification = helpers.create_notification(name=long_name) + self.assertRaises(exceptions.UnprocessableEntity, + self.monasca_client.create_notifications, + notification) + + @test.attr(type="gate") + @test.attr(type=['negative']) + def test_create_notification_method_with_address_exceeds_max_length(self): + long_address = "x" * ( + constants.MAX_NOTIFICATION_METHOD_ADDRESS_LENGTH + 1) + notification = helpers.create_notification(address=long_address) + self.assertRaises(exceptions.UnprocessableEntity, + self.monasca_client.create_notifications, + notification) + + @test.attr(type="gate") + @test.attr(type=['negative']) + def test_create_notification_method_with_invalid_type(self): + notification = helpers.create_notification(type='random') + self.assertRaises(exceptions.BadRequest, + self.monasca_client.create_notifications, + notification) + + @test.attr(type="gate") + def test_list_notification_methods(self): + resp, body = self.monasca_client.list_notification_methods() + self.assertEqual(200, resp.status) + + @test.attr(type="gate") + def test_list_notification_methods_with_offset_limit(self): + query_parms = '?offset=1&limit=2' + resp, body = self.monasca_client.list_notification_methods(query_parms) + self.assertEqual(200, resp.status) + + @test.attr(type="gate") + def test_list_notification_methods_response_body(self): + # TODO(RMH): Validate response body + resp, response_body = self.monasca_client.list_notification_methods() + self.assertTrue(set(['links', 'elements']) == set(response_body)) + elements = response_body['elements'] + element = elements[0] + self.assertTrue(set(['id', 'links', 'name', 'type', 'address']) == + set(element)) + # check if 'id' is an int. NOPE its unicode + self.assertTrue(type(element['id']) is unicode) + # check if 'links' is link + self.assertTrue(type(element['links']) is list) + # check if 'name' is a string. NOPE its unicode + self.assertTrue(type(element['name']) is unicode) + # check if 'type' is an unicode + self.assertTrue(type(element['type']) is unicode) + # check if 'address' is an unicode + self.assertTrue(type(element['address']) is unicode) + + @test.attr(type="gate") + def test_get_notification_method(self): + notification = helpers.create_notification() + resp, response_body = self.monasca_client.create_notifications( + notification) + self.assertEqual(201, resp.status) + id = response_body['id'] + resp, response_body = self.monasca_client.get_notification_method(id) + self.assertEqual(200, resp.status) + resp, response_body = self.monasca_client.\ + delete_notification_method(id) + self.assertEqual(204, resp.status) + + @test.attr(type="gate") + @test.attr(type=['negative']) + def test_get_notification_method_with_invalid_id(self): + notification = helpers.create_notification() + resp, response_body = self.monasca_client.create_notifications( + notification) + self.assertEqual(201, resp.status) + id = data_utils.rand_name() + self.assertRaises(exceptions.NotFound, + self.monasca_client.get_notification_method, + id) + resp, response_body = self.monasca_client.\ + delete_notification_method(response_body['id']) + self.assertEqual(204, resp.status) + + @test.attr(type="gate") + def test_update_notification_method_name(self): + name = data_utils.rand_name('notification-') + notification = helpers.create_notification(name=name) + resp, response_body = self.monasca_client.create_notifications( + notification) + self.assertEqual(201, resp.status) + self.assertEqual(name, response_body['name']) + id = response_body['id'] + new_name = name + 'update' + resp, response_body = self.monasca_client.\ + update_notification_method(id, new_name, + type=response_body['type'], + address=response_body['address']) + self.assertEqual(200, resp.status) + self.assertEqual(new_name, response_body['name']) + resp, response_body = self.monasca_client.\ + delete_notification_method(id) + self.assertEqual(204, resp.status) + + @test.attr(type="gate") + def test_update_notification_method_type(self): + type = 'EMAIL' + notification = helpers.create_notification(type=type) + resp, response_body = self.monasca_client.create_notifications( + notification) + self.assertEqual(201, resp.status) + self.assertEqual(type, response_body['type']) + id = response_body['id'] + new_type = 'PAGERDUTY' + resp, response_body = \ + self.monasca_client.\ + update_notification_method(id, name=response_body['name'], + type=new_type, + address=response_body['address']) + self.assertEqual(200, resp.status) + self.assertEqual(new_type, response_body['type']) + resp, response_body = self.monasca_client.\ + delete_notification_method(id) + self.assertEqual(204, resp.status) + + @test.attr(type="gate") + def test_update_notification_method_address(self): + address = DEFAULT_EMAIL_ADDRESS + notification = helpers.create_notification(address=address) + resp, response_body = self.monasca_client.create_notifications( + notification) + self.assertEqual(201, resp.status) + self.assertEqual(address, response_body['address']) + id = response_body['id'] + new_address = 'jane.doe@domain.com' + resp, response_body = self.monasca_client.\ + update_notification_method(id, + name=response_body['name'], + type=response_body['type'], + address=new_address) + self.assertEqual(200, resp.status) + self.assertEqual(new_address, response_body['address']) + resp, response_body = \ + self.monasca_client.delete_notification_method(id) + self.assertEqual(204, resp.status) + + @test.attr(type="gate") + @test.attr(type=['negative']) + def test_update_notification_method_name_exceeds_max_length(self): + name = data_utils.rand_name('notification-') + notification = helpers.create_notification(name=name) + resp, response_body = self.monasca_client.create_notifications( + notification) + id = response_body['id'] + self.assertEqual(201, resp.status) + new_name_long = "x" * (constants.MAX_NOTIFICATION_METHOD_NAME_LENGTH + + 1) + self.assertRaises(exceptions.UnprocessableEntity, + self.monasca_client.update_notification_method, id, + name=new_name_long, type=response_body['type'], + address=response_body['address']) + resp, response_body = \ + self.monasca_client.delete_notification_method(id) + self.assertEqual(204, resp.status) + + @test.attr(type="gate") + @test.attr(type=['negative']) + def test_update_notification_method_invalid_type(self): + name = data_utils.rand_name('notification-') + notification = helpers.create_notification(name=name) + resp, response_body = self.monasca_client.create_notifications( + notification) + id = response_body['id'] + self.assertEqual(201, resp.status) + self.assertRaises(exceptions.BadRequest, + self.monasca_client.update_notification_method, id, + name=response_body['name'], type='random', + address=response_body['address']) + resp, response_body = \ + self.monasca_client.delete_notification_method(id) + self.assertEqual(204, resp.status) + + @test.attr(type="gate") + @test.attr(type=['negative']) + def test_update_notification_method_address_exceeds_max_length(self): + name = data_utils.rand_name('notification-') + notification = helpers.create_notification(name=name) + resp, response_body = self.monasca_client.create_notifications( + notification) + id = response_body['id'] + self.assertEqual(201, resp.status) + new_address_long = "x" * ( + constants.MAX_NOTIFICATION_METHOD_ADDRESS_LENGTH + 1) + self.assertRaises(exceptions.BadRequest, + self.monasca_client.update_notification_method, id, + name=response_body['name'], type=new_address_long, + address=response_body['address']) + resp, response_body = \ + self.monasca_client.delete_notification_method(id) + self.assertEqual(204, resp.status) + + @test.attr(type="gate") + def test_delete_notification_method(self): + notification = helpers.create_notification() + resp, response_body = self.monasca_client.create_notifications( + notification) + self.assertEqual(201, resp.status) + id = response_body['id'] + resp, response_body = self.monasca_client.\ + delete_notification_method(id) + self.assertEqual(204, resp.status) + + @test.attr(type="gate") + @test.attr(type=['negative']) + def test_delete_notification_method_with_invalid_id(self): + name = data_utils.rand_name('notification-') + notification = helpers.create_notification(name=name) + resp, response_body = self.monasca_client.create_notifications( + notification) + self.assertEqual(201, resp.status) + id = data_utils.rand_name() + self.assertRaises(exceptions.NotFound, + self.monasca_client.delete_notification_method, + id) + resp, response_body = self.monasca_client.\ + delete_notification_method(response_body['id']) + self.assertEqual(204, resp.status) diff --git a/monasca_tempest_tests/tests/api/test_statistics.py b/monasca_tempest_tests/tests/api/test_statistics.py new file mode 100644 index 000000000..f3a01f6c8 --- /dev/null +++ b/monasca_tempest_tests/tests/api/test_statistics.py @@ -0,0 +1,301 @@ +# (C) Copyright 2015 Hewlett Packard Enterprise Development Company LP +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import time + +from oslo_utils import timeutils + +from monasca_tempest_tests.tests.api import base +from monasca_tempest_tests.tests.api import constants +from monasca_tempest_tests.tests.api import helpers +from tempest.common.utils import data_utils +from tempest import test +from tempest_lib import exceptions + +NUM_MEASUREMENTS = 100 +WAIT_TIME = 30 + + +class TestStatistics(base.BaseMonascaTest): + + @classmethod + def resource_setup(cls): + super(TestStatistics, cls).resource_setup() + + start_timestamp = int(time.time() * 1000) + end_timestamp = int(time.time() * 1000) + NUM_MEASUREMENTS * 1000 + metrics = [] + + for i in xrange(NUM_MEASUREMENTS): + metric = helpers.create_metric( + name="name-1", + timestamp=start_timestamp + i) + metrics.append(metric) + + resp, response_body = cls.monasca_client.create_metrics(metric) + cls._start_timestamp = start_timestamp + cls._end_timestamp = end_timestamp + cls._metrics = metrics + + @classmethod + def resource_cleanup(cls): + super(TestStatistics, cls).resource_cleanup() + + @test.attr(type="gate") + def test_list_statistics(self): + start_time = timeutils.iso8601_from_timestamp(self._start_timestamp / + 1000) + query_parms = '?name=name-1&merge_metrics=true&statistics=avg' \ + '&start_time=' + str(start_time) + resp, response_body = self.monasca_client.list_statistics( + query_parms) + self.assertEqual(200, resp.status) + + self.assertTrue(set(['links', 'elements']) == set(response_body)) + elements = response_body['elements'] + element = elements[0] + self.assertTrue(set(['id', 'name', 'dimensions', 'columns', + 'statistics']) == set(element)) + # check if 'id' is unicode type + self.assertTrue(type(element['id']) is unicode) + # check if 'name' is a string. NOPE its unicode + self.assertTrue(type(element['name']) is unicode) + self.assertTrue(type(element['dimensions']) is dict) + self.assertTrue(type(element['columns']) is list) + self.assertTrue(type(element['statistics']) is list) + statistic = element['statistics'] + column = element['columns'] + self.assertTrue(type(statistic) is list) + self.assertTrue(type(column) is list) + + @test.attr(type="gate") + @test.attr(type=['negative']) + def test_list_statistics_with_no_name(self): + start_time = timeutils.iso8601_from_timestamp(self._start_timestamp / + 1000) + query_parms = '?merge_metrics=true&statistics=avg&start_time=' + \ + str(start_time) + self.assertRaises(exceptions.UnprocessableEntity, + self.monasca_client.list_statistics, query_parms) + + @test.attr(type="gate") + @test.attr(type=['negative']) + def test_list_statistics_with_no_statistics(self): + start_time = timeutils.iso8601_from_timestamp(self._start_timestamp / + 1000) + query_parms = '?name=name-1&start_time=' + str(start_time) + self.assertRaises(exceptions.UnprocessableEntity, + self.monasca_client.list_statistics, query_parms) + + @test.attr(type="gate") + @test.attr(type=['negative']) + def test_list_statistics_with_no_start_time(self): + query_parms = '?name=name-1&statistics=avg' + self.assertRaises(exceptions.UnprocessableEntity, + self.monasca_client.list_statistics, query_parms) + + @test.attr(type="gate") + @test.attr(type=['negative']) + def test_list_statistics_with_invalid_statistics(self): + start_time = timeutils.iso8601_from_timestamp( + self._start_timestamp / 1000) + query_parms = '?name=name-1&statistics=abc&start_time=' + str( + start_time) + self.assertRaises(exceptions.UnprocessableEntity, + self.monasca_client.list_statistics, query_parms) + + @test.attr(type="gate") + def test_list_statistics_with_dimensions(self): + start_time = timeutils.iso8601_from_timestamp(self._start_timestamp + / 1000) + query_parms = '?name=name-1&merge_metrics=true&statistics=avg&' \ + 'start_time=' + str(start_time) + \ + '&dimensions=key1:value1' + resp, response_body = self.monasca_client.list_statistics( + query_parms) + self.assertEqual(200, resp.status) + + @test.attr(type="gate") + def test_list_statistics_with_end_time(self): + start_time = timeutils.iso8601_from_timestamp( + self._start_timestamp / 1000) + end_time = timeutils.iso8601_from_timestamp( + self._end_timestamp / 1000) + query_parms = '?name=name-1&merge_metrics=true&statistics=avg&' \ + 'start_time=' + str(start_time) + '&end_time=' + \ + str(end_time) + resp, response_body = self.monasca_client.list_statistics( + query_parms) + self.assertEqual(200, resp.status) + + @test.attr(type="gate") + def test_list_statistics_with_period(self): + start_time = timeutils.iso8601_from_timestamp( + self._start_timestamp / 1000) + query_parms = '?name=name-1&merge_metrics=true&statistics=avg&' \ + 'start_time=' + str(start_time) + '&period=300' + resp, response_body = self.monasca_client.list_statistics( + query_parms) + self.assertEqual(200, resp.status) + + @test.attr(type="gate") + def test_list_statistics_with_offset_limit(self): + start_timestamp = int(time.time() * 1000) + name = data_utils.rand_name() + metric = [ + helpers.create_metric(name=name, timestamp=start_timestamp + 0, + dimensions={'key1': 'value-1', + 'key2': 'value-1'}, + value=1), + helpers.create_metric(name=name, timestamp=start_timestamp + 500, + dimensions={'key1': 'value-2', + 'key2': 'value-2'}, + value=2), + helpers.create_metric(name=name, timestamp=start_timestamp + 1000, + dimensions={'key1': 'value-3', + 'key2': 'value-3'}, + value=3), + helpers.create_metric(name=name, timestamp=start_timestamp + 1500, + dimensions={'key1': 'value-4', + 'key2': 'value-4'}, + value=4), + helpers.create_metric(name=name, timestamp=start_timestamp + 2000, + dimensions={'key1': 'value-2', + 'key2': 'value-2'}, + value=5), + helpers.create_metric(name=name, timestamp=start_timestamp + 2500, + dimensions={'key1': 'value-3', + 'key2': 'value-3'}, + value=6), + helpers.create_metric(name=name, timestamp=start_timestamp + 3000, + dimensions={'key1': 'value-4', + 'key2': 'value-4'}, + value=7), + helpers.create_metric(name=name, timestamp=start_timestamp + 3500, + dimensions={'key1': 'value-4', + 'key2': 'value-4'}, + value=8) + ] + + resp, response_body = self.monasca_client.create_metrics(metric) + time.sleep(WAIT_TIME) + + query_parms = '?name=' + name + resp, response_body = self.monasca_client.list_metrics(query_parms) + self.assertEqual(200, resp.status) + + start_time = timeutils.iso8601_from_timestamp( + start_timestamp / 1000) + end_timestamp = start_timestamp + 4000 + end_time = timeutils.iso8601_from_timestamp(end_timestamp / 1000) + query_parms = '?name=' + name + '&merge_metrics=true&statistics=avg,' \ + 'max,min,sum,count&start_time=' + str(start_time) + \ + '&end_time=' + str(end_time) + '&period=1' + resp, body = self.monasca_client.list_statistics(query_parms) + self.assertEqual(200, resp.status) + elements = body['elements'][0]['statistics'] + first_element = elements[0] + last_element = elements[3] + + query_parms = '?name=' + name + '&merge_metrics=true&statistics=avg,' \ + 'max,min,sum,count&start_time=' + str(start_time) + \ + '&end_time=' + str(end_time) + '&period=1' + '&limit=4' + resp, response_body = self.monasca_client.list_statistics( + query_parms) + self.assertEqual(200, resp.status) + elements = response_body['elements'][0]['statistics'] + self.assertEqual(4, len(elements)) + self.assertEqual(first_element, elements[0]) + + for limit in xrange(1, 5): + next_element = elements[limit - 1] + offset_timestamp = start_timestamp + while True: + offset_timestamp += 1000 * limit + offset = timeutils.iso8601_from_timestamp(offset_timestamp / + 1000) + query_parms = '?name=' + name + '&merge_metrics=true' + \ + '&statistics=avg,max,min,sum,' \ + 'count&start_time=' + str(start_time) + \ + '&end_time=' + str(end_time) + '&period=1' + \ + '&limit=' + str(limit) + '&offset=' + str(offset) + resp, response_body = self.monasca_client.list_statistics( + query_parms) + self.assertEqual(200, resp.status) + new_elements = response_body['elements'][0]['statistics'] + + if len(new_elements) > limit - 1: + self.assertEqual(limit, len(new_elements)) + next_element = new_elements[limit - 1] + elif len(new_elements) > 0 and len(new_elements) <= limit - 1: + self.assertEqual(last_element, new_elements[0]) + break + else: + self.assertEqual(last_element, next_element) + break + + @test.attr(type="gate") + def test_list_statistics_with_merge_metrics(self): + start_time = timeutils.iso8601_from_timestamp( + self._start_timestamp / 1000) + query_parms = '?name=name-1&merge_metrics=true&statistics=avg&' \ + 'merge_metrics=true&start_time=' + str(start_time) + resp, response_body = self.monasca_client.list_statistics( + query_parms) + self.assertEqual(200, resp.status) + + @test.attr(type="gate") + @test.attr(type=['negative']) + def test_list_statistics_with_no_merge_metrics(self): + start_time = timeutils.\ + iso8601_from_timestamp(self._start_timestamp / 1000) + query_parms = '?name=name-1&merge_metrics=false&' \ + 'statistics=avg,min,max&start_time=' + str(start_time) + self.assertRaises(exceptions.Conflict, + self.monasca_client.list_statistics, query_parms) + + @test.attr(type="gate") + @test.attr(type=['negative']) + def test_list_statistics_with_name_exceeds_max_length(self): + long_name = "x" * (constants.MAX_LIST_STATISTICS_NAME_LENGTH + 1) + start_time = timeutils.iso8601_from_timestamp(self._start_timestamp + / 1000) + query_parms = '?merge_metrics=true&name=' + str(long_name) + \ + '&start_time=' + str(start_time) + self.assertRaises(exceptions.UnprocessableEntity, + self.monasca_client.list_statistics, query_parms) + + @test.attr(type="gate") + def test_list_statistics_with_more_than_one_statistics(self): + start_time = timeutils.\ + iso8601_from_timestamp(self._start_timestamp / 1000) + query_parms = '?name=name-1&merge_metrics=true&' \ + 'statistics=avg,min,max&start_time=' + str(start_time) + resp, response_body = self.monasca_client.list_statistics( + query_parms) + self.assertEqual(200, resp.status) + + @test.attr(type="gate") + def test_list_statistics_response_body_statistic_result_type(self): + start_time = timeutils.iso8601_from_timestamp(self._start_timestamp + / 1000) + query_parms = '?name=name-1&merge_metrics=true&statistics=avg&' \ + 'start_time=' + str(start_time) + resp, response_body = self.monasca_client.list_statistics( + query_parms) + self.assertEqual(200, resp.status) + element = response_body['elements'][0] + statistic = element['statistics'] + statistic_result_type = type(statistic[0][1]) + self.assertEqual(statistic_result_type, float) diff --git a/monasca_tempest_tests/tests/api/test_versions.py b/monasca_tempest_tests/tests/api/test_versions.py new file mode 100644 index 000000000..d62a43b9c --- /dev/null +++ b/monasca_tempest_tests/tests/api/test_versions.py @@ -0,0 +1,51 @@ +# (C) Copyright 2015 Hewlett Packard Enterprise Development Company LP +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import datetime + +from oslo_serialization import jsonutils as json + +from monasca_tempest_tests.tests.api import base +from tempest import test + + +class TestVersions(base.BaseMonascaTest): + + @classmethod + def resource_setup(cls): + super(TestVersions, cls).resource_setup() + + @test.attr(type='gate') + def test_get_version(self): + resp, response_body = self.monasca_client.get_version() + self.assertEqual(resp.status, 200) + response_body = json.loads(response_body) + + self.assertTrue(isinstance(response_body, dict)) + version = response_body + self.assertTrue(set(['id', 'links', 'status', 'updated']) == + set(version)) + self.assertEqual(version['id'], u'v2.0') + self.assertEqual(version['status'], u'CURRENT') + date_object = datetime.datetime.strptime(version['updated'], + "%Y-%m-%dT%H:%M:%S.%fZ") + self.assertTrue(isinstance(date_object, datetime.datetime)) + links = response_body['links'] + self.assertTrue(isinstance(links, list)) + link = links[0] + self.assertTrue(set(['rel', 'href']) == + set(link)) + self.assertEqual(link['rel'], u'self') + self.assertTrue(link['href'].endswith('/v2.0')) + return diff --git a/setup.cfg b/setup.cfg index 404345e13..326e6aa9f 100755 --- a/setup.cfg +++ b/setup.cfg @@ -20,9 +20,10 @@ classifier = [files] packages = monasca_api + monasca_tempest_tests data_files = - /etc/monasca = + etc/monasca = etc/api-config.conf etc/api-config.ini @@ -30,5 +31,8 @@ data_files = console_scripts = monasca-api = monasca_api.api.server:launch +tempest.test_plugins = + monasca_tests = monasca_tempest_tests.plugin:MonascaTempestPlugin + [pbr] -warnerrors = True \ No newline at end of file +warnerrors = True diff --git a/tox.ini b/tox.ini index 796329ce0..3df5a55e4 100644 --- a/tox.ini +++ b/tox.ini @@ -9,7 +9,7 @@ usedevelop = True install_command = pip install -U {opts} {packages} deps = -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt -commands = nosetests +commands = python setup.py testr --testr-args='{posargs}' [testenv:cover] setenv = NOSE_WITH_COVERAGE=1