diff --git a/etc/rally/rally.conf.sample b/etc/rally/rally.conf.sample index b2e5c4cd9d..c2118c6b4e 100644 --- a/etc/rally/rally.conf.sample +++ b/etc/rally/rally.conf.sample @@ -268,6 +268,9 @@ # (floating point value) #manila_share_delete_poll_interval = 2.0 +# mistral execution timeout (integer value) +#mistral_execution_timeout = 200 + # Delay between creating Monasca metrics and polling for its elements. # (floating point value) #monasca_metric_create_prepoll_delay = 15.0 diff --git a/rally-jobs/rally-mistral.yaml b/rally-jobs/rally-mistral.yaml index 9c545087d4..e0c9281dd9 100644 --- a/rally-jobs/rally-mistral.yaml +++ b/rally-jobs/rally-mistral.yaml @@ -44,3 +44,35 @@ sla: failure_rate: max: 0 + + MistralExecutions.list_executions: + - + runner: + type: "constant" + times: 50 + concurrency: 10 + context: + users: + tenants: 2 + users_per_tenant: 2 + sla: + failure_rate: + max: 0 + + MistralExecutions.create_execution_from_workbook: + - + args: + definition: "~/.rally/extra/mistral_wb.yaml" + workflow_name: "wf1" + do_delete: true + runner: + type: "constant" + times: 50 + concurrency: 10 + context: + users: + tenants: 2 + users_per_tenant: 2 + sla: + failure_rate: + max: 0 \ No newline at end of file diff --git a/rally/common/opts.py b/rally/common/opts.py index 85ccc2cae4..99c60f1b93 100644 --- a/rally/common/opts.py +++ b/rally/common/opts.py @@ -25,6 +25,7 @@ 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.magnum import utils as magnum_utils from rally.plugins.openstack.scenarios.manila import utils as manila_utils +from rally.plugins.openstack.scenarios.mistral import utils as mistral_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 @@ -48,6 +49,7 @@ def list_opts(): ironic_utils.IRONIC_BENCHMARK_OPTS, magnum_utils.MAGNUM_BENCHMARK_OPTS, manila_utils.MANILA_BENCHMARK_OPTS, + mistral_utils.MISTRAL_BENCHMARK_OPTS, monasca_utils.MONASCA_BENCHMARK_OPTS, murano_utils.MURANO_BENCHMARK_OPTS, nova_utils.NOVA_BENCHMARK_OPTS, diff --git a/rally/plugins/openstack/cleanup/resources.py b/rally/plugins/openstack/cleanup/resources.py index ebc9456db6..ff517e346e 100644 --- a/rally/plugins/openstack/cleanup/resources.py +++ b/rally/plugins/openstack/cleanup/resources.py @@ -736,10 +736,32 @@ class SwiftContainer(SwiftMixin): # MISTRAL -@base.resource("mistral", "workbooks", order=1100, tenant_resource=True) -class MistralWorkbooks(SynchronizedDeletion, base.ResourceManager): +_mistral_order = get_order(1100) + + +class MistralMixin(SynchronizedDeletion, base.ResourceManager): + def delete(self): - self._manager().delete(self.raw_resource.name) + self._manager().delete(self.raw_resource["id"]) + + +@base.resource("mistral", "workbooks", order=next(_mistral_order), + tenant_resource=True) +class MistralWorkbooks(MistralMixin): + def delete(self): + self._manager().delete(self.raw_resource["name"]) + + +@base.resource("mistral", "workflows", order=next(_mistral_order), + tenant_resource=True) +class MistralWorkflows(MistralMixin): + pass + + +@base.resource("mistral", "executions", order=next(_mistral_order), + tenant_resource=True) +class MistralExecutions(MistralMixin): + pass # MURANO diff --git a/rally/plugins/openstack/scenarios/mistral/executions.py b/rally/plugins/openstack/scenarios/mistral/executions.py new file mode 100644 index 0000000000..f4d1dea899 --- /dev/null +++ b/rally/plugins/openstack/scenarios/mistral/executions.py @@ -0,0 +1,92 @@ +# 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 six +import yaml + +from rally import consts +from rally.plugins.openstack import scenario +from rally.plugins.openstack.scenarios.mistral import utils +from rally.task import types +from rally.task import validation + + +"""Scenarios for Mistral execution.""" + + +@validation.required_clients("mistral") +@validation.required_openstack(users=True) +@validation.required_services(consts.Service.MISTRAL) +@scenario.configure(name="MistralExecutions.list_executions", + context={"cleanup": ["mistral"]}) +class ListExecutions(utils.MistralScenario): + + def run(self, marker="", limit=None, sort_keys="", sort_dirs=""): + """Scenario test mistral execution-list command. + + This simple scenario tests the Mistral execution-list + command by listing all the executions. + :param marker: The last execution uuid of the previous page, displays + list of executions after "marker". + :param limit: number Maximum number of executions to return in a single + result. + :param sort_keys: id,description + :param sort_dirs: [SORT_DIRS] Comma-separated list of sort directions. + Default: asc. + """ + self._list_executions(marker=marker, limit=limit, + sort_keys=sort_keys, sort_dirs=sort_dirs) + + +@validation.required_parameters("definition") +@validation.file_exists("definition") +@types.convert(definition={"type": "file"}) +@validation.required_clients("mistral") +@validation.required_openstack(users=True) +@validation.required_services(consts.Service.MISTRAL) +@validation.workbook_contains_workflow("definition", "workflow_name") +@scenario.configure( + name="MistralExecutions.create_execution_from_workbook", + context={"cleanup": ["mistral"]}) +class CreateExecutionFromWorkbook(utils.MistralScenario): + + def run(self, definition, workflow_name=None, do_delete=False): + """Scenario tests execution creation and deletion. + + This scenario is a very useful tool to measure the + "mistral execution-create" and "mistral execution-delete" + commands performance. + :param definition: string (yaml string) representation of given file + content (Mistral workbook definition) + :param workflow_name: string the workflow name to execute. Should be + one of the to workflows in the definition. If no + workflow_name is passed, one of the workflows in + the definition will be taken. + + :param do_delete: if False than it allows to check performance + in "create only" mode. + """ + + wb = self._create_workbook(definition) + wb_def = yaml.safe_load(wb.definition) + + if not workflow_name: + workflow_name = six.next(six.iterkeys(wb_def["workflows"])) + + workflow_identifier = ".".join([wb.name, workflow_name]) + ex = self._create_execution(workflow_identifier) + + if do_delete: + self._delete_workbook(wb.name) + self._delete_execution(ex) diff --git a/rally/plugins/openstack/scenarios/mistral/utils.py b/rally/plugins/openstack/scenarios/mistral/utils.py index 2d6b81f4a9..b480b60d75 100644 --- a/rally/plugins/openstack/scenarios/mistral/utils.py +++ b/rally/plugins/openstack/scenarios/mistral/utils.py @@ -13,10 +13,23 @@ # License for the specific language governing permissions and limitations # under the License. +from oslo_config import cfg import yaml from rally.plugins.openstack import scenario from rally.task import atomic +from rally.task import utils + +MISTRAL_BENCHMARK_OPTS = [ + cfg.IntOpt( + "mistral_execution_timeout", + default=200, + help="mistral execution timeout") +] + +CONF = cfg.CONF +benchmark_group = cfg.OptGroup(name="benchmark", title="benchmark options") +CONF.register_opts(MISTRAL_BENCHMARK_OPTS, group=benchmark_group) class MistralScenario(scenario.OpenStackScenario): @@ -24,7 +37,10 @@ class MistralScenario(scenario.OpenStackScenario): @atomic.action_timer("mistral.list_workbooks") def _list_workbooks(self): - """Gets list of existing workbooks.""" + """Gets list of existing workbooks. + + :returns: workbook list + """ return self.clients("mistral").workbooks.list() @atomic.action_timer("mistral.create_workbook") @@ -48,3 +64,41 @@ class MistralScenario(scenario.OpenStackScenario): :param wb_name: the name of workbook that would be deleted. """ self.clients("mistral").workbooks.delete(wb_name) + + @atomic.action_timer("mistral.list_executions") + def _list_executions(self, marker="", limit=None, sort_keys="", + sort_dirs=""): + """Gets list of existing executions. + + :returns: execution list + """ + + return self.clients("mistral").executions.list( + marker=marker, limit=limit, sort_keys=sort_keys, + sort_dirs=sort_dirs) + + @atomic.action_timer("mistral.create_execution") + def _create_execution(self, workflow_identifier): + """Create a new execution. + + :param workflow_identifier: name or id of the workflow to execute + :returns: executions object + """ + + execution = self.clients("mistral").executions.create( + workflow_identifier) + + execution = utils.wait_for_status( + execution, ready_statuses=["SUCCESS"], failure_statuses=["ERROR"], + update_resource=utils.get_from_manager(), + timeout=CONF.benchmark.mistral_execution_timeout) + + return execution + + @atomic.action_timer("mistral.delete_execution") + def _delete_execution(self, execution): + """Delete the given execution. + + :param ex: the execution that would be deleted. + """ + self.clients("mistral").executions.delete(execution.id) diff --git a/rally/plugins/openstack/scenarios/mistral/workbooks.py b/rally/plugins/openstack/scenarios/mistral/workbooks.py index 1de4d1df6e..08c737f12c 100644 --- a/rally/plugins/openstack/scenarios/mistral/workbooks.py +++ b/rally/plugins/openstack/scenarios/mistral/workbooks.py @@ -38,9 +38,9 @@ class ListWorkbooks(utils.MistralScenario): self._list_workbooks() -@types.convert(definition={"type": "file"}) -@validation.file_exists("definition") @validation.required_parameters("definition") +@validation.file_exists("definition") +@types.convert(definition={"type": "file"}) @validation.required_clients("mistral") @validation.required_openstack(users=True) @validation.required_services(consts.Service.MISTRAL) @@ -62,4 +62,4 @@ class CreateWorkbook(utils.MistralScenario): wb = self._create_workbook(definition) if do_delete: - self._delete_workbook(wb.name) \ No newline at end of file + self._delete_workbook(wb.name) diff --git a/rally/task/validation.py b/rally/task/validation.py index 518ea0e63d..f8f6eae9a0 100755 --- a/rally/task/validation.py +++ b/rally/task/validation.py @@ -21,6 +21,7 @@ import re from glanceclient import exc as glance_exc from novaclient import exceptions as nova_exc import six +import yaml from rally.common.i18n import _ from rally.common import objects @@ -727,3 +728,30 @@ def validate_heat_template(config, clients, deployment, *param_names): msg = (_("Heat template validation failed on %(path)s. " "Original error message: %(msg)s.") % dct) return ValidationResult(False, msg) + + +@validator +def workbook_contains_workflow(config, clients, deployment, workbook, + workflow_name): + """Validate that workflow exist in workbook when workflow is passed + + :param workbook: parameter containing the workbook definition + :param workflow_name: parameter containing the workflow name + """ + + wf_name = config.get("args", {}).get(workflow_name) + if wf_name: + wb_path = config.get("args", {}).get(workbook) + wb_path = os.path.expanduser(wb_path) + file_result = _file_access_ok(config.get("args", {}).get(workbook), + os.R_OK, workbook) + if not file_result.is_valid: + return file_result + + with open(wb_path, "r") as wb_def: + wb_def = yaml.safe_load(wb_def) + if wf_name not in wb_def["workflows"]: + return ValidationResult( + False, + "workflow '{}' not found in the definition '{}'".format( + wf_name, wb_def)) diff --git a/samples/tasks/scenarios/mistral/create-delete-execution-with-workflow-name.json b/samples/tasks/scenarios/mistral/create-delete-execution-with-workflow-name.json new file mode 100644 index 0000000000..e14fcb6d50 --- /dev/null +++ b/samples/tasks/scenarios/mistral/create-delete-execution-with-workflow-name.json @@ -0,0 +1,27 @@ +{ + "MistralExecutions.create_execution_from_workbook": [ + { + "args": { + "definition": "rally-jobs/extra/mistral_wb.yaml", + "workflow_name": "wf1", + "do_delete": true + }, + "runner": { + "type": "constant", + "times": 20, + "concurrency": 5 + }, + "context": { + "users": { + "tenants": 2, + "users_per_tenant": 2 + } + }, + "sla": { + "failure_rate": { + "max": 0 + } + } + } + ] +} diff --git a/samples/tasks/scenarios/mistral/create-delete-execution-with-workflow-name.yaml b/samples/tasks/scenarios/mistral/create-delete-execution-with-workflow-name.yaml new file mode 100644 index 0000000000..8d2dc426ed --- /dev/null +++ b/samples/tasks/scenarios/mistral/create-delete-execution-with-workflow-name.yaml @@ -0,0 +1,20 @@ +--- + MistralExecutions.create_execution_from_workbook: + - + args: + definition: rally-jobs/extra/mistral_wb.yaml + workflow_name: wf1 + do_delete: true + runner: + type: "constant" + times: 20 + concurrency: 5 + context: + users: + tenants: 2 + users_per_tenant: 2 + sla: + failure_rate: + max: 0 + + diff --git a/samples/tasks/scenarios/mistral/create-delete-execution.json b/samples/tasks/scenarios/mistral/create-delete-execution.json new file mode 100644 index 0000000000..c715b68027 --- /dev/null +++ b/samples/tasks/scenarios/mistral/create-delete-execution.json @@ -0,0 +1,26 @@ +{ + "MistralExecutions.create_execution_from_workbook": [ + { + "args": { + "definition": "rally-jobs/extra/mistral_wb.yaml", + "do_delete": true + }, + "runner": { + "type": "constant", + "times": 20, + "concurrency": 5 + }, + "context": { + "users": { + "tenants": 2, + "users_per_tenant": 2 + } + }, + "sla": { + "failure_rate": { + "max": 0 + } + } + } + ] +} diff --git a/samples/tasks/scenarios/mistral/create-delete-execution.yaml b/samples/tasks/scenarios/mistral/create-delete-execution.yaml new file mode 100644 index 0000000000..5c8e20d25c --- /dev/null +++ b/samples/tasks/scenarios/mistral/create-delete-execution.yaml @@ -0,0 +1,18 @@ +--- + MistralExecutions.create_execution_from_workbook: + - + args: + definition: rally-jobs/extra/mistral_wb.yaml + do_delete: true + runner: + type: "constant" + times: 20 + concurrency: 5 + context: + users: + tenants: 2 + users_per_tenant: 2 + sla: + failure_rate: + max: 0 + diff --git a/samples/tasks/scenarios/mistral/create-execution-with-workflow-name.json b/samples/tasks/scenarios/mistral/create-execution-with-workflow-name.json new file mode 100644 index 0000000000..f871443886 --- /dev/null +++ b/samples/tasks/scenarios/mistral/create-execution-with-workflow-name.json @@ -0,0 +1,26 @@ +{ + "MistralExecutions.create_execution_from_workbook": [ + { + "args": { + "definition": "rally-jobs/extra/mistral_wb.yaml", + "workflow_name": "wf1" + }, + "runner": { + "type": "constant", + "times": 20, + "concurrency": 5 + }, + "context": { + "users": { + "tenants": 2, + "users_per_tenant": 2 + } + }, + "sla": { + "failure_rate": { + "max": 0 + } + } + } + ] +} diff --git a/samples/tasks/scenarios/mistral/create-execution-with-workflow-name.yaml b/samples/tasks/scenarios/mistral/create-execution-with-workflow-name.yaml new file mode 100644 index 0000000000..ec24be619e --- /dev/null +++ b/samples/tasks/scenarios/mistral/create-execution-with-workflow-name.yaml @@ -0,0 +1,18 @@ +--- + MistralExecutions.create_execution_from_workbook: + - + args: + definition: rally-jobs/extra/mistral_wb.yaml + workflow_name: wf1 + runner: + type: "constant" + times: 20 + concurrency: 5 + context: + users: + tenants: 2 + users_per_tenant: 2 + sla: + failure_rate: + max: 0 + diff --git a/samples/tasks/scenarios/mistral/create-execution.json b/samples/tasks/scenarios/mistral/create-execution.json new file mode 100644 index 0000000000..4d3e5de4b4 --- /dev/null +++ b/samples/tasks/scenarios/mistral/create-execution.json @@ -0,0 +1,25 @@ +{ + "MistralExecutions.create_execution_from_workbook": [ + { + "args": { + "definition": "rally-jobs/extra/mistral_wb.yaml" + }, + "runner": { + "type": "constant", + "times": 20, + "concurrency": 5 + }, + "context": { + "users": { + "tenants": 2, + "users_per_tenant": 2 + } + }, + "sla": { + "failure_rate": { + "max": 0 + } + } + } + ] +} diff --git a/samples/tasks/scenarios/mistral/create-execution.yaml b/samples/tasks/scenarios/mistral/create-execution.yaml new file mode 100644 index 0000000000..2d8c01a7ca --- /dev/null +++ b/samples/tasks/scenarios/mistral/create-execution.yaml @@ -0,0 +1,16 @@ +--- + MistralExecutions.create_execution_from_workbook: + - + args: + definition: rally-jobs/extra/mistral_wb.yaml + runner: + type: "constant" + times: 20 + concurrency: 5 + context: + users: + tenants: 2 + users_per_tenant: 2 + sla: + failure_rate: + max: 0 diff --git a/samples/tasks/scenarios/mistral/list-executions.json b/samples/tasks/scenarios/mistral/list-executions.json new file mode 100644 index 0000000000..be342ced1c --- /dev/null +++ b/samples/tasks/scenarios/mistral/list-executions.json @@ -0,0 +1,22 @@ +{ + "MistralExecutions.list_executions": [ + { + "runner": { + "type": "constant", + "times": 50, + "concurrency": 10 + }, + "context": { + "users": { + "tenants": 2, + "users_per_tenant": 2 + } + }, + "sla": { + "failure_rate": { + "max": 0 + } + } + } + ] +} diff --git a/samples/tasks/scenarios/mistral/list-executions.yaml b/samples/tasks/scenarios/mistral/list-executions.yaml new file mode 100644 index 0000000000..5eae18247d --- /dev/null +++ b/samples/tasks/scenarios/mistral/list-executions.yaml @@ -0,0 +1,14 @@ +--- + MistralExecutions.list_executions: + - + runner: + type: "constant" + times: 50 + concurrency: 10 + context: + users: + tenants: 2 + users_per_tenant: 2 + sla: + failure_rate: + max: 0 diff --git a/tests/ci/osresources.py b/tests/ci/osresources.py index 134a95d2d0..cf6a9e30cb 100755 --- a/tests/ci/osresources.py +++ b/tests/ci/osresources.py @@ -119,6 +119,20 @@ class Magnum(ResourceManager): return result +class Mistral(ResourceManager): + + REQUIRED_SERVICE = consts.Service.MISTRAL + + def list_workbooks(self): + return self.client.workbooks.list() + + def list_workflows(self): + return self.client.workflows.list() + + def list_executions(self): + return self.client.executions.list() + + class Nova(ResourceManager): REQUIRED_SERVICE = consts.Service.NOVA diff --git a/tests/unit/fakes.py b/tests/unit/fakes.py index 007c4339ce..7e1bf6512c 100644 --- a/tests/unit/fakes.py +++ b/tests/unit/fakes.py @@ -318,6 +318,18 @@ class FakeWorkbook(FakeResource): self.workbook = mock.MagicMock() +class FakeWorkflow(FakeResource): + def __init__(self, manager=None): + super(FakeWorkflow, self).__init__(manager) + self.workflow = mock.MagicMock() + + +class FakeExecution(FakeResource): + def __init__(self, manager=None): + super(FakeExecution, self).__init__(manager) + self.execution = mock.MagicMock() + + class FakeObject(FakeResource): pass @@ -939,6 +951,27 @@ class FakeWorkbookManager(FakeManager): return [self.workbook] +class FakeWorkflowManager(FakeManager): + def __init__(self): + super(FakeWorkflowManager, self).__init__() + self.workflow = FakeWorkflow() + + def list(self): + return [self.workflow] + + +class FakeExecutionManager(FakeManager): + def __init__(self): + super(FakeExecutionManager, self).__init__() + self.execution = FakeExecution() + + def list(self): + return [self.execution] + + def create(self): + return self.execution + + class FakeObjectManager(FakeManager): def get_account(self, **kwargs): @@ -1505,6 +1538,8 @@ class FakeMistralClient(object): def __init__(self): self.workbook = FakeWorkbookManager() + self.workflow = FakeWorkflowManager() + self.execution = FakeExecutionManager() class FakeSwiftClient(FakeObjectManager): diff --git a/tests/unit/plugins/openstack/cleanup/test_resources.py b/tests/unit/plugins/openstack/cleanup/test_resources.py index f6cb540978..ab8fd14ed1 100644 --- a/tests/unit/plugins/openstack/cleanup/test_resources.py +++ b/tests/unit/plugins/openstack/cleanup/test_resources.py @@ -760,6 +760,36 @@ class ManilaSecurityServiceTestCase(test.TestCase): "fake_id") +class MistralMixinTestCase(test.TestCase): + + def test_delete(self): + mistral = resources.MistralMixin() + mistral._service = "mistral" + mistral.user = mock.MagicMock() + mistral._resource = "some_resources" + mistral.raw_resource = {"id": "TEST_ID"} + mistral.user.mistral().some_resources.delete.return_value = None + + mistral.delete() + mistral.user.mistral().some_resources.delete.assert_called_once_with( + "TEST_ID") + + +class MistralWorkbookTestCase(test.TestCase): + + def test_delete(self): + mistral = resources.MistralWorkbooks() + mistral._service = "mistral" + mistral.user = mock.MagicMock() + mistral._resource = "some_resources" + mistral.raw_resource = {"name": "TEST_NAME"} + mistral.user.mistral().some_resources.delete.return_value = None + + mistral.delete() + mistral.user.mistral().some_resources.delete.assert_called_once_with( + "TEST_NAME") + + class FuelEnvironmentTestCase(test.TestCase): def test_id(self): diff --git a/tests/unit/plugins/openstack/scenarios/mistral/test_executions.py b/tests/unit/plugins/openstack/scenarios/mistral/test_executions.py new file mode 100644 index 0000000000..aaef71358c --- /dev/null +++ b/tests/unit/plugins/openstack/scenarios/mistral/test_executions.py @@ -0,0 +1,162 @@ +# Copyright 2016: Nokia Inc. +# 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 + +from rally.plugins.openstack.scenarios.mistral import executions +from tests.unit import test + +BASE = "rally.plugins.openstack.scenarios.mistral.executions" +MISTRAL_WBS_BASE = "rally.plugins.openstack.scenarios.mistral.workbooks" + + +WB_DEFINITION = """--- +version: 2.0 +name: wb +workflows: + wf1: + type: direct + tasks: + noop_task: + action: std.noop + wf2: + type: direct + tasks: + noop_task: + action: std.noop + wf3: + type: direct + tasks: + noop_task: + action: std.noop + wf4: + type: direct + tasks: + noop_task: + action: std.noop +""" + +WB_DEF_ONE_WF = """--- +version: 2.0 +name: wb +workflows: + wf1: + type: direct + tasks: + noop_task: + action: std.noop +""" + +WB = type("obj", (object,), {"name": "wb", "definition": WB_DEFINITION})() +WB_ONE_WF = ( + type("obj", (object,), {"name": "wb", "definition": WB_DEF_ONE_WF})() +) + + +class MistralExecutionsTestCase(test.ScenarioTestCase): + + @mock.patch("%s.ListExecutions._list_executions" % BASE) + def test_list_executions(self, mock__list_executions): + executions.ListExecutions(self.context).run() + self.assertEqual(1, mock__list_executions.called) + + @mock.patch("%s.CreateExecutionFromWorkbook._create_execution" % BASE) + @mock.patch("%s.CreateExecutionFromWorkbook._create_workbook" % BASE, + return_value=WB) + def test_create_execution(self, mock__create_workbook, + mock__create_execution): + + executions.CreateExecutionFromWorkbook(self.context).run(WB_DEFINITION) + + self.assertEqual(1, mock__create_workbook.called) + self.assertEqual(1, mock__create_execution.called) + + @mock.patch("%s.CreateExecutionFromWorkbook._create_execution" % BASE) + @mock.patch("%s.CreateExecutionFromWorkbook._create_workbook" % BASE, + return_value=WB) + def test_create_execution_with_wf_name(self, mock__create_workbook, + mock__create_execution): + + executions.CreateExecutionFromWorkbook(self.context).run( + WB_DEFINITION, "wf4") + + self.assertEqual(1, mock__create_workbook.called) + self.assertEqual(1, mock__create_execution.called) + + # we concatenate workbook name with the workflow name in the test + # the workbook name is not random because we mock the method that + # adds the random part + mock__create_execution.assert_called_once_with("wb.wf4") + + @mock.patch("%s.CreateExecutionFromWorkbook._delete_execution" % BASE) + @mock.patch("%s.CreateExecutionFromWorkbook._delete_workbook" % BASE) + @mock.patch("%s.CreateExecutionFromWorkbook._create_execution" % BASE) + @mock.patch("%s.CreateExecutionFromWorkbook._create_workbook" % BASE, + return_value=WB) + def test_create_delete_execution( + self, mock__create_workbook, mock__create_execution, + mock__delete_workbook, mock__delete_execution): + + executions.CreateExecutionFromWorkbook(self.context).run( + WB_DEFINITION, do_delete=True) + + self.assertEqual(1, mock__create_workbook.called) + self.assertEqual(1, mock__create_execution.called) + self.assertEqual(1, mock__delete_workbook.called) + self.assertEqual(1, mock__delete_execution.called) + + @mock.patch("%s.CreateExecutionFromWorkbook._delete_execution" % BASE) + @mock.patch("%s.CreateExecutionFromWorkbook._delete_workbook" % BASE) + @mock.patch("%s.CreateExecutionFromWorkbook._create_execution" % BASE) + @mock.patch("%s.CreateExecutionFromWorkbook._create_workbook" % BASE, + return_value=WB) + def test_create_delete_execution_with_wf_name( + self, mock__create_workbook, mock__create_execution, + mock__delete_workbook, mock__delete_execution): + + executions.CreateExecutionFromWorkbook(self.context).run( + WB_DEFINITION, "wf4", do_delete=True) + + self.assertEqual(1, mock__create_workbook.called) + self.assertEqual(1, mock__create_execution.called) + self.assertEqual(1, mock__delete_workbook.called) + self.assertEqual(1, mock__delete_execution.called) + + # we concatenate workbook name with the workflow name in the test + # the workbook name is not random because we mock the method that + # adds the random part + mock__create_execution.assert_called_once_with("wb.wf4") + + @mock.patch("%s.CreateExecutionFromWorkbook._delete_execution" % BASE) + @mock.patch("%s.CreateExecutionFromWorkbook._delete_workbook" % BASE) + @mock.patch("%s.CreateExecutionFromWorkbook._create_execution" % BASE) + @mock.patch("%s.CreateExecutionFromWorkbook._create_workbook" % BASE, + return_value=WB_ONE_WF) + def test_create_delete_execution_without_wf_name( + self, mock__create_workbook, mock__create_execution, + mock__delete_workbook, mock__delete_execution): + + executions.CreateExecutionFromWorkbook(self.context).run( + WB_DEF_ONE_WF, do_delete=True) + + self.assertEqual(1, mock__create_workbook.called) + self.assertEqual(1, mock__create_execution.called) + self.assertEqual(1, mock__delete_workbook.called) + self.assertEqual(1, mock__delete_execution.called) + + # we concatenate workbook name with the workflow name in the test + # the workbook name is not random because we mock the method that + # adds the random part + mock__create_execution.assert_called_once_with("wb.wf1") diff --git a/tests/unit/plugins/openstack/scenarios/mistral/test_utils.py b/tests/unit/plugins/openstack/scenarios/mistral/test_utils.py index e753253fb2..7801cac441 100644 --- a/tests/unit/plugins/openstack/scenarios/mistral/test_utils.py +++ b/tests/unit/plugins/openstack/scenarios/mistral/test_utils.py @@ -14,6 +14,7 @@ # under the License. from rally.plugins.openstack.scenarios.mistral import utils +from tests.unit import fakes from tests.unit import test MISTRAL_UTILS = "rally.plugins.openstack.scenarios.mistral.utils" @@ -27,21 +28,77 @@ class MistralScenarioTestCase(test.ScenarioTestCase): self.assertEqual( self.clients("mistral").workbooks.list.return_value, return_wbs_list) - self._test_atomic_action_timer(scenario.atomic_actions(), - "mistral.list_workbooks") + self._test_atomic_action_timer( + scenario.atomic_actions(), + "mistral.list_workbooks" + ) def test_create_workbook(self): definition = "version: \"2.0\"\nname: wb" scenario = utils.MistralScenario(context=self.context) - self.assertEqual(scenario._create_workbook(definition), - self.clients("mistral").workbooks.create.return_value) - self._test_atomic_action_timer(scenario.atomic_actions(), - "mistral.create_workbook") + self.assertEqual( + self.clients("mistral").workbooks.create.return_value, + scenario._create_workbook(definition) + ) + self._test_atomic_action_timer( + scenario.atomic_actions(), + "mistral.create_workbook" + ) def test_delete_workbook(self): scenario = utils.MistralScenario(context=self.context) scenario._delete_workbook("wb_name") self.clients("mistral").workbooks.delete.assert_called_once_with( - "wb_name") - self._test_atomic_action_timer(scenario.atomic_actions(), - "mistral.delete_workbook") + "wb_name" + ) + self._test_atomic_action_timer( + scenario.atomic_actions(), + "mistral.delete_workbook" + ) + + def test_list_executions(self): + scenario = utils.MistralScenario(context=self.context) + return_executions_list = scenario._list_executions() + self.assertEqual( + return_executions_list, + self.clients("mistral").executions.list.return_value + ) + self._test_atomic_action_timer( + scenario.atomic_actions(), + "mistral.list_executions" + ) + + def test_create_execution(self): + scenario = utils.MistralScenario(context=self.context) + + mock_wait_for_status = self.mock_wait_for_status.mock + wf_name = "fake_wf_name" + mock_create_exec = self.clients("mistral").executions.create + + self.assertEqual( + mock_wait_for_status.return_value, + scenario._create_execution("%s" % wf_name) + ) + + mock_create_exec.assert_called_once_with(wf_name) + + args, kwargs = mock_wait_for_status.call_args + self.assertEqual(mock_create_exec.return_value, args[0]) + self.assertEqual(["ERROR"], kwargs["failure_statuses"]) + self.assertEqual(["SUCCESS"], kwargs["ready_statuses"]) + self._test_atomic_action_timer( + scenario.atomic_actions(), + "mistral.create_execution" + ) + + def test_delete_execution(self): + scenario = utils.MistralScenario(context=self.context) + execution = fakes.FakeMistralClient().execution.create() + scenario._delete_execution(execution) + self.clients("mistral").executions.delete.assert_called_once_with( + execution.id + ) + self._test_atomic_action_timer( + scenario.atomic_actions(), + "mistral.delete_execution" + ) \ No newline at end of file diff --git a/tests/unit/task/test_validation.py b/tests/unit/task/test_validation.py index 572f7cad6c..bad292fc68 100755 --- a/tests/unit/task/test_validation.py +++ b/tests/unit/task/test_validation.py @@ -974,3 +974,44 @@ class ValidatorsTestCase(test.TestCase): config = {"context": {"api_versions": {"nova": {"version": 2}}}} self.assertEqual(validator(config, clients, None).is_valid, valid) + + @mock.patch( + "yaml.safe_load", + return_value={ + "version": "2.0", + "name": "wb", + "workflows": { + "wf1": { + "type": "direct", + "tasks": { + "t1": { + "action": "std.noop" + } + } + } + } + } + ) + @mock.patch(MODULE + "os.access") + @mock.patch(MODULE + "open") + def test_workbook_contains_workflow(self, mock_open, mock_access, + mock_safe_load): + + validator = self._unwrap_validator( + validation.workbook_contains_workflow, "definition", + "workflow_name") + clients = mock.MagicMock() + + context = { + "args": { + "definition": "fake_path1", + "workflow_name": "wf1" + } + } + + result = validator(context, clients, None) + self.assertTrue(result.is_valid) + + self.assertEqual(1, mock_open.called) + self.assertEqual(1, mock_access.called) + self.assertEqual(1, mock_safe_load.called)