Add option to create mistral execution from workbook

Change-Id: I637ff8cd73c788c519a1af5962fd923a6a12d0da
This commit is contained in:
Michal Gershenzon 2016-08-21 22:50:05 +00:00
parent 5ad4a6e9a1
commit efe719e911
21 changed files with 729 additions and 16 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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)
self._delete_workbook(wb.name)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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