diff --git a/optional-requirements.txt b/optional-requirements.txt index fb3f6a458f..adaa59db51 100644 --- a/optional-requirements.txt +++ b/optional-requirements.txt @@ -1,7 +1,6 @@ python-mistralclient>=2.0.0 python-fuelclient==6.1.0 # Apache Software License python-muranoclient>=0.8.2 # Apache License, Version 2.0 -python-monascaclient>=1.1.0 # Apache Software License python-cueclient>=1.0.0,<=1.0.0 # Apache License, Version 2.0 python-senlinclient>=0.3.0 # Apache Software License python-magnumclient>=2.0.0 # Apache Software License diff --git a/rally-jobs/rally-monasca.yaml b/rally-jobs/rally-monasca.yaml new file mode 100644 index 0000000000..8de3400a4d --- /dev/null +++ b/rally-jobs/rally-monasca.yaml @@ -0,0 +1,39 @@ +--- + MonascaMetrics.list_metrics: + - + runner: + type: "constant" + times: 10 + concurrency: 2 + context: + users: + tenants: 2 + users_per_tenant: 2 + roles: + - "monasca-user" + monasca_metrics: + "dimensions": + "region": "RegionOne" + "service": "identity" + "hostname": "fake_host" + "url": "http://fake_host:5000/v2.0" + "metrics_per_tenant": 10 + sla: + failure_rate: + max: 0 + - + runner: + type: "constant" + times: 10 + concurrency: 2 + context: + users: + tenants: 2 + users_per_tenant: 2 + roles: + - "monasca-user" + monasca_metrics: + "metrics_per_tenant": 10 + sla: + failure_rate: + max: 0 diff --git a/rally/common/opts.py b/rally/common/opts.py index 1e23c42385..c9648be158 100644 --- a/rally/common/opts.py +++ b/rally/common/opts.py @@ -24,6 +24,7 @@ from rally.plugins.openstack.scenarios.ec2 import utils as ec2_utils from rally.plugins.openstack.scenarios.heat import utils as heat_utils from rally.plugins.openstack.scenarios.ironic import utils as ironic_utils from rally.plugins.openstack.scenarios.manila import utils as manila_utils +from rally.plugins.openstack.scenarios.monasca import utils as monasca_utils from rally.plugins.openstack.scenarios.murano import utils as murano_utils from rally.plugins.openstack.scenarios.nova import utils as nova_utils from rally.plugins.openstack.scenarios.sahara import utils as sahara_utils @@ -44,6 +45,7 @@ def list_opts(): heat_utils.HEAT_BENCHMARK_OPTS, ironic_utils.IRONIC_BENCHMARK_OPTS, manila_utils.MANILA_BENCHMARK_OPTS, + monasca_utils.MONASCA_BENCHMARK_OPTS, murano_utils.MURANO_BENCHMARK_OPTS, nova_utils.NOVA_BENCHMARK_OPTS, sahara_utils.SAHARA_BENCHMARK_OPTS, diff --git a/rally/plugins/openstack/context/monasca/__init__.py b/rally/plugins/openstack/context/monasca/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/rally/plugins/openstack/context/monasca/metrics.py b/rally/plugins/openstack/context/monasca/metrics.py new file mode 100644 index 0000000000..9d31e9370e --- /dev/null +++ b/rally/plugins/openstack/context/monasca/metrics.py @@ -0,0 +1,106 @@ +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from six import moves + +from rally.common.i18n import _ +from rally.common import logging +from rally.common import utils as rutils +from rally import consts +from rally.plugins.openstack.scenarios.monasca import utils as monasca_utils +from rally.task import context + + +LOG = logging.getLogger(__name__) + + +@context.configure(name="monasca_metrics", order=510) +class MonascaMetricGenerator(context.Context): + """Context for creating metrics for benchmarks.""" + + CONFIG_SCHEMA = { + "type": "object", + "$schema": consts.JSON_SCHEMA, + "properties": { + "name": { + "type": "string" + }, + "dimensions": { + "type": "object", + "properties": { + "region": { + "type": "string" + }, + "service": { + "type": "string" + }, + "hostname": { + "type": "string" + }, + "url": { + "type": "string" + } + } + }, + "metrics_per_tenant": { + "type": "integer", + "minimum": 1 + }, + "value_meta": { + "type": "array", + "items": { + "type": "object", + "properties": { + "value_meta_key": { + "type": "string" + }, + "value_meta_value": { + "type": "string" + } + } + } + } + }, + "additionalProperties": False + } + + DEFAULT_CONFIG = { + "metrics_per_tenant": 2 + } + + @logging.log_task_wrapper(LOG.info, _("Enter context: `Monasca`")) + def setup(self): + new_metric = {} + + if "dimensions" in self.config: + new_metric = { + "dimensions": self.config["dimensions"] + } + + for user, tenant_id in rutils.iterate_per_tenants( + self.context["users"]): + scenario = monasca_utils.MonascaScenario( + context={"user": user, "task": self.context["task"]} + ) + for i in moves.xrange(self.config["metrics_per_tenant"]): + scenario._create_metrics(**new_metric) + rutils.interruptable_sleep(0.001) + rutils.interruptable_sleep( + monasca_utils.CONF.benchmark.monasca_metric_create_prepoll_delay, + atomic_delay=1) + + @logging.log_task_wrapper(LOG.info, _("Exit context: `Monasca`")) + def cleanup(self): + # We don't have API for removal of metrics + pass diff --git a/rally/plugins/openstack/scenarios/monasca/__init__.py b/rally/plugins/openstack/scenarios/monasca/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/rally/plugins/openstack/scenarios/monasca/metrics.py b/rally/plugins/openstack/scenarios/monasca/metrics.py new file mode 100644 index 0000000000..fcd2e5d594 --- /dev/null +++ b/rally/plugins/openstack/scenarios/monasca/metrics.py @@ -0,0 +1,37 @@ +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from rally.common import log as logging +from rally import consts +from rally.plugins.openstack import scenario +from rally.plugins.openstack.scenarios.monasca import utils as monascautils +from rally.task import validation + +LOG = logging.getLogger(__name__) + + +class MonascaMetrics(monascautils.MonascaScenario): + """Benchmark scenarios for monasca Metrics API.""" + + @validation.required_clients("monasca") + @validation.required_services(consts.Service.MONASCA) + @validation.required_openstack(users=True) + @scenario.configure() + def list_metrics(self, **kwargs): + """Fetch user's metrics. + + :param kwargs: optional arguments for list query: + name, dimensions, start_time, etc + """ + self._list_metrics(**kwargs) diff --git a/rally/plugins/openstack/scenarios/monasca/utils.py b/rally/plugins/openstack/scenarios/monasca/utils.py new file mode 100644 index 0000000000..6d787c8bdb --- /dev/null +++ b/rally/plugins/openstack/scenarios/monasca/utils.py @@ -0,0 +1,64 @@ +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import random +import time +import uuid + +from oslo_config import cfg + +from rally.plugins.openstack import scenario +from rally.task import atomic + + +MONASCA_BENCHMARK_OPTS = [ + cfg.FloatOpt( + "monasca_metric_create_prepoll_delay", + default=15.0, + help="Delay between creating Monasca metrics and polling for " + "its elements.") +] + +CONF = cfg.CONF +benchmark_group = cfg.OptGroup(name="benchmark", title="benchmark options") +CONF.register_opts(MONASCA_BENCHMARK_OPTS, group=benchmark_group) + + +class MonascaScenario(scenario.OpenStackScenario): + """Base class for Monasca scenarios with basic atomic actions.""" + + @atomic.action_timer("monasca.list_metrics") + def _list_metrics(self, **kwargs): + """Get list of user's metrics. + + :param kwargs: optional arguments for list query: + name, dimensions, start_time, etc + :returns list of monasca metrics + """ + return self.clients("monasca").metrics.list(**kwargs) + + @atomic.action_timer("monasca.create_metrics") + def _create_metrics(self, **kwargs): + """Create user metrics. + + :param kwargs: attributes for metric creation: + name, dimension, timestamp, value, etc + """ + timestamp = int(time.time() * 1000) + kwargs.update({"name": self.generate_random_name(), + "timestamp": timestamp, + "value": random.random(), + "value_meta": { + "key": str(uuid.uuid4())[:10]}}) + self.clients("monasca").metrics.create(**kwargs) diff --git a/requirements.txt b/requirements.txt index 7a957abe7e..1a32dce5a4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -27,6 +27,7 @@ python-novaclient!=2.33.0,>=2.29.0 # Apache License, Version python-neutronclient>=4.2.0 # Apache Software License python-cinderclient!=1.7.0,!=1.7.1,>=1.6.0 # Apache Software License python-manilaclient>=1.10.0 # Apache Software License +python-monascaclient>=1.2.0 # Apache Software License python-heatclient>=1.1.0 # Apache Software License python-ceilometerclient>=2.2.1 # Apache Software License python-ironicclient>=1.1.0 # Apache Software License diff --git a/samples/tasks/scenarios/monasca/list-metrics.json b/samples/tasks/scenarios/monasca/list-metrics.json new file mode 100644 index 0000000000..7f09ca934f --- /dev/null +++ b/samples/tasks/scenarios/monasca/list-metrics.json @@ -0,0 +1,33 @@ +{ + "MonascaMetrics.list_metrics": [ + { + "runner": { + "type": "constant", + "times": 10, + "concurrency": 1 + }, + "context": { + "users": { + "tenants": 1, + "users_per_tenant": 1 + }, + "roles": [ + "monasca-user" + ], + "monasca_metrics": { + "dimensions": { + "region": "RegionOne", + "service": "identity", + "hostname": "fake_host", + "url": "http://fake_host:5000/v2.0" + }, + "metrics_per_tenant": 10 + } + }, + "args": { + "region": "RegionOne", + "limit": 5 + } + } + ] +} diff --git a/samples/tasks/scenarios/monasca/list-metrics.yaml b/samples/tasks/scenarios/monasca/list-metrics.yaml new file mode 100644 index 0000000000..76a99e66ae --- /dev/null +++ b/samples/tasks/scenarios/monasca/list-metrics.yaml @@ -0,0 +1,23 @@ +--- + MonascaMetrics.list_metrics: + - + runner: + type: "constant" + times: 10 + concurrency: 1 + context: + users: + tenants: 1 + users_per_tenant: 1 + roles: + - "monasca-user" + monasca_metrics: + "dimensions": + "region": "RegionOne" + "service": "identity" + "hostname": "fake_host" + "url": "http://fake_host:5000/v2.0" + "metrics_per_tenant": 10 + args: + "region": "RegionOne" + "limit": 5 \ No newline at end of file diff --git a/tests/unit/plugins/openstack/context/monasca/__init__.py b/tests/unit/plugins/openstack/context/monasca/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/unit/plugins/openstack/context/monasca/test_metrics.py b/tests/unit/plugins/openstack/context/monasca/test_metrics.py new file mode 100644 index 0000000000..11ce2b971e --- /dev/null +++ b/tests/unit/plugins/openstack/context/monasca/test_metrics.py @@ -0,0 +1,100 @@ +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import mock +import six + +from rally.plugins.openstack.context.monasca import metrics +from rally.plugins.openstack.scenarios.monasca import utils as monasca_utils +from tests.unit import test + +CTX = "rally.plugins.openstack.context.monasca" + + +class MonascaMetricGeneratorTestCase(test.TestCase): + + def _gen_tenants(self, count): + tenants = {} + for id in six.moves.range(count): + tenants[str(id)] = {"name": str(id)} + return tenants + + def _gen_context(self, tenants_count, users_per_tenant, + metrics_per_tenant): + tenants = self._gen_tenants(tenants_count) + users = [] + for id in tenants.keys(): + for i in six.moves.range(users_per_tenant): + users.append({"id": i, "tenant_id": id, + "endpoint": mock.MagicMock()}) + context = test.get_test_context() + context.update({ + "config": { + "users": { + "tenants": tenants_count, + "users_per_tenant": users_per_tenant, + "concurrent": 10, + }, + "monasca_metrics": { + "name": "fake-metric-name", + "dimensions": { + "region": "fake-region", + "service": "fake-identity", + "hostname": "fake-hostname", + "url": "fake-url" + }, + "metrics_per_tenant": metrics_per_tenant, + }, + "roles": [ + "monasca-user" + ] + }, + "admin": { + "endpoint": mock.MagicMock() + }, + "users": users, + "tenants": tenants + }) + return tenants, context + + @mock.patch("%s.metrics.rutils.interruptable_sleep" % CTX) + @mock.patch("%s.metrics.monasca_utils.MonascaScenario" % CTX) + def test_setup(self, mock_monasca_scenario, mock_interruptable_sleep): + tenants_count = 2 + users_per_tenant = 4 + metrics_per_tenant = 5 + + tenants, real_context = self._gen_context( + tenants_count, users_per_tenant, metrics_per_tenant) + + monasca_ctx = metrics.MonascaMetricGenerator(real_context) + monasca_ctx.setup() + + self.assertEqual(tenants_count, mock_monasca_scenario.call_count, + "Scenario should be constructed same times as " + "number of tenants") + self.assertEqual(metrics_per_tenant * tenants_count, + mock_monasca_scenario.return_value._create_metrics. + call_count, + "Total number of metrics created should be tenant" + "counts times metrics per tenant") + first_call = mock.call(0.001) + second_call = mock.call(monasca_utils.CONF.benchmark. + monasca_metric_create_prepoll_delay, + atomic_delay=1) + self.assertEqual([first_call] * metrics_per_tenant * tenants_count + + [second_call], + mock_interruptable_sleep.call_args_list, + "Method interruptable_sleep should be called " + "tenant counts times metrics plus one") diff --git a/tests/unit/plugins/openstack/scenarios/monasca/__init__.py b/tests/unit/plugins/openstack/scenarios/monasca/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/unit/plugins/openstack/scenarios/monasca/test_metrics.py b/tests/unit/plugins/openstack/scenarios/monasca/test_metrics.py new file mode 100644 index 0000000000..baf833d9bf --- /dev/null +++ b/tests/unit/plugins/openstack/scenarios/monasca/test_metrics.py @@ -0,0 +1,35 @@ +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import ddt +import mock + +from rally.plugins.openstack.scenarios.monasca import metrics +from tests.unit import test + + +@ddt.ddt +class MonascaMetricsTestCase(test.ScenarioTestCase): + + @ddt.data( + {"region": None}, + {"region": "fake_region"}, + ) + @ddt.unpack + def test_list_metrics(self, region=None): + scenario = metrics.MonascaMetrics() + self.region = region + scenario._list_metrics = mock.MagicMock() + scenario.list_metrics(region=self.region) + scenario._list_metrics.assert_called_once_with(region=self.region) diff --git a/tests/unit/plugins/openstack/scenarios/monasca/test_utils.py b/tests/unit/plugins/openstack/scenarios/monasca/test_utils.py new file mode 100644 index 0000000000..18891b8e03 --- /dev/null +++ b/tests/unit/plugins/openstack/scenarios/monasca/test_utils.py @@ -0,0 +1,51 @@ +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import ddt + +from rally.plugins.openstack.scenarios.monasca import utils +from tests.unit import test + + +@ddt.ddt +class MonascaScenarioTestCase(test.ScenarioTestCase): + + def setUp(self): + super(MonascaScenarioTestCase, self).setUp() + self.scenario = utils.MonascaScenario(self.context) + self.kwargs = { + "dimensions": { + "region": "fake_region", + "hostname": "fake_host_name", + "service": "fake_service", + "url": "fake_url" + } + } + + def test_list_metrics(self): + return_metric_value = self.scenario._list_metrics() + self.assertEqual(return_metric_value, + self.clients("monasca").metrics.list.return_value) + self._test_atomic_action_timer(self.scenario.atomic_actions(), + "monasca.list_metrics") + + @ddt.data( + {"name": ""}, + {"name": "fake_metric"}, + ) + @ddt.unpack + def test_create_metrics(self, name=None): + self.name = name + self.scenario._create_metrics(name=self.name, kwargs=self.kwargs) + self.assertEqual(1, self.clients("monasca").metrics.create.call_count)