Add Ironic scenarios
Add Ironic scenarios: * create_and_list_node * create_and_delete_node Add Ironic related utils: * _create_node * _list_nodes * _delete_node Add unit tests for them. Add Ironic to _Service and BARE_METAL to _ServiceType. Also there is a new rally-ironic.yaml job file. Change-Id: I5bfa68d2f9dc30680996d932c5b03b01d6c0a0fa
This commit is contained in:
parent
e736a18ced
commit
8dd702c904
18
rally-jobs/rally-ironic.yaml
Normal file
18
rally-jobs/rally-ironic.yaml
Normal file
@ -0,0 +1,18 @@
|
||||
---
|
||||
{% for s in ("create_and_list_node", "create_and_delete_node") %}
|
||||
IronicNodes.{{s}}:
|
||||
-
|
||||
args:
|
||||
driver: "pxe_ssh"
|
||||
runner:
|
||||
type: "constant"
|
||||
times: 10
|
||||
concurrency: 2
|
||||
context:
|
||||
users:
|
||||
tenants: 5
|
||||
users_per_tenant: 1
|
||||
sla:
|
||||
failure_rate:
|
||||
max: 0
|
||||
{% endfor %}
|
@ -105,6 +105,7 @@ class _Service(utils.ImmutableMixin, utils.EnumMixin):
|
||||
SWIFT = "swift"
|
||||
MISTRAL = "mistral"
|
||||
MURANO = "murano"
|
||||
IRONIC = "ironic"
|
||||
|
||||
|
||||
class _ServiceType(utils.ImmutableMixin, utils.EnumMixin):
|
||||
@ -130,6 +131,7 @@ class _ServiceType(utils.ImmutableMixin, utils.EnumMixin):
|
||||
OBJECT_STORE = "object-store"
|
||||
WORKFLOW_EXECUTION = "workflowv2"
|
||||
APPLICATION_CATALOG = "application_catalog"
|
||||
BARE_METAL = "baremetal"
|
||||
|
||||
def __init__(self):
|
||||
self.__names = {
|
||||
@ -152,7 +154,8 @@ class _ServiceType(utils.ImmutableMixin, utils.EnumMixin):
|
||||
self.DATA_PROCESSING: _Service.SAHARA,
|
||||
self.OBJECT_STORE: _Service.SWIFT,
|
||||
self.WORKFLOW_EXECUTION: _Service.MISTRAL,
|
||||
self.APPLICATION_CATALOG: _Service.MURANO
|
||||
self.APPLICATION_CATALOG: _Service.MURANO,
|
||||
self.BARE_METAL: _Service.IRONIC,
|
||||
}
|
||||
|
||||
def __getitem__(self, service_type):
|
||||
|
78
rally/plugins/openstack/scenarios/ironic/nodes.py
Normal file
78
rally/plugins/openstack/scenarios/ironic/nodes.py
Normal file
@ -0,0 +1,78 @@
|
||||
# Copyright 2015: Mirantis 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.
|
||||
|
||||
from rally import consts
|
||||
from rally.plugins.openstack.scenarios.ironic import utils
|
||||
from rally.task.scenarios import base
|
||||
from rally.task import validation
|
||||
|
||||
|
||||
class IronicNodes(utils.IronicScenario):
|
||||
"""Base class for Ironic scenarios with basic atomic actions."""
|
||||
|
||||
@validation.required_parameters("driver")
|
||||
@validation.required_services(consts.Service.IRONIC)
|
||||
@validation.required_openstack(admin=True)
|
||||
@base.scenario(context={"admin_cleanup": ["ironic"]})
|
||||
def create_and_list_node(
|
||||
self, associated=None, maintenance=None,
|
||||
marker=None, limit=None, detail=False, sort_key=None,
|
||||
sort_dir=None, **kwargs):
|
||||
"""Create and list nodes.
|
||||
|
||||
:param associated: Optional. Either a Boolean or a string
|
||||
representation of a Boolean that indicates whether
|
||||
to return a list of associated (True or "True") or
|
||||
unassociated (False or "False") nodes.
|
||||
:param maintenance: Optional. Either a Boolean or a string
|
||||
representation of a Boolean that indicates whether
|
||||
to return nodes in maintenance mode (True or
|
||||
"True"), or not in maintenance mode (False or
|
||||
"False").
|
||||
:param marker: Optional, the UUID of a node, eg the last
|
||||
node from a previous result set. Return
|
||||
the next result set.
|
||||
:param limit: The maximum number of results to return per
|
||||
request, if:
|
||||
1) limit > 0, the maximum number of nodes to return.
|
||||
2) limit == 0, return the entire list of nodes.
|
||||
3) limit param is NOT specified (None), the number of items
|
||||
returned respect the maximum imposed by the Ironic API
|
||||
(see Ironic's api.max_limit option).
|
||||
:param detail: Optional, boolean whether to return detailed
|
||||
information about nodes.
|
||||
:param sort_key: Optional, field used for sorting.
|
||||
:param sort_dir: Optional, direction of sorting, either 'asc' (the
|
||||
default) or 'desc'.
|
||||
:param kwargs: Optional additional arguments for node creation
|
||||
"""
|
||||
|
||||
self._create_node(**kwargs)
|
||||
|
||||
self._list_nodes(
|
||||
associated=associated, maintenance=maintenance, marker=marker,
|
||||
limit=limit, detail=detail, sort_key=sort_key, sort_dir=sort_dir)
|
||||
|
||||
@validation.required_parameters("driver")
|
||||
@validation.required_services(consts.Service.IRONIC)
|
||||
@validation.required_openstack(admin=True)
|
||||
@base.scenario(context={"admin_cleanup": ["ironic"]})
|
||||
def create_and_delete_node(self, **kwargs):
|
||||
"""Create and delete node.
|
||||
|
||||
:param kwargs: Optional additional arguments for node creation
|
||||
"""
|
||||
node = self._create_node(**kwargs)
|
||||
self._delete_node(node.uuid)
|
102
rally/plugins/openstack/scenarios/ironic/utils.py
Normal file
102
rally/plugins/openstack/scenarios/ironic/utils.py
Normal file
@ -0,0 +1,102 @@
|
||||
# Copyright 2015: Mirantis 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 string
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
|
||||
from rally.common import utils
|
||||
from rally.plugins.openstack import scenario
|
||||
from rally.task.scenarios import base
|
||||
|
||||
|
||||
IRONIC_BENCHMARK_OPTS = [
|
||||
cfg.FloatOpt("ironic_node_create_poll_interval",
|
||||
default=1.0,
|
||||
help="Interval(in sec) between checks when waiting for node "
|
||||
"creation."),
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
benchmark_group = cfg.OptGroup(name="benchmark", title="benchmark options")
|
||||
CONF.register_opts(IRONIC_BENCHMARK_OPTS, group=benchmark_group)
|
||||
|
||||
|
||||
class IronicScenario(scenario.OpenStackScenario):
|
||||
"""Base class for Ironic scenarios with basic atomic actions."""
|
||||
|
||||
@base.atomic_action_timer("ironic.create_node")
|
||||
def _create_node(self, **kwargs):
|
||||
"""Create node immediately.
|
||||
|
||||
:param kwargs: optional parameters to create image
|
||||
:returns: node object
|
||||
"""
|
||||
if "name" not in kwargs:
|
||||
# NOTE(rvasilets): can't use _generate_random_name() because
|
||||
# ironic have specific format for node name.
|
||||
# Check that the supplied hostname conforms to:
|
||||
# * http://en.wikipedia.org/wiki/Hostname
|
||||
# * http://tools.ietf.org/html/rfc952
|
||||
# * http://tools.ietf.org/html/rfc1123
|
||||
# or the name could be just uuid.
|
||||
kwargs["name"] = utils.generate_random_name(
|
||||
prefix="rally", choice=string.ascii_lowercase + string.digits)
|
||||
|
||||
return self.admin_clients("ironic").node.create(**kwargs)
|
||||
|
||||
@base.atomic_action_timer("ironic.list_nodes")
|
||||
def _list_nodes(self, associated=None, maintenance=None, marker=None,
|
||||
limit=None, detail=False, sort_key=None, sort_dir=None):
|
||||
"""Return list of nodes.
|
||||
|
||||
:param associated: Optional. Either a Boolean or a string
|
||||
representation of a Boolean that indicates whether
|
||||
to return a list of associated (True or "True") or
|
||||
unassociated (False or "False") nodes.
|
||||
:param maintenance: Optional. Either a Boolean or a string
|
||||
representation of a Boolean that indicates whether
|
||||
to return nodes in maintenance mode (True or
|
||||
"True"), or not in maintenance mode (False or
|
||||
"False").
|
||||
:param marker: Optional, the UUID of a node, eg the last
|
||||
node from a previous result set. Return
|
||||
the next result set.
|
||||
:param limit: The maximum number of results to return per
|
||||
request, if:
|
||||
1) limit > 0, the maximum number of nodes to return.
|
||||
2) limit == 0, return the entire list of nodes.
|
||||
3) limit param is NOT specified (None), the number of items
|
||||
returned respect the maximum imposed by the Ironic API
|
||||
(see Ironic's api.max_limit option).
|
||||
:param detail: Optional, boolean whether to return detailed information
|
||||
about nodes.
|
||||
:param sort_key: Optional, field used for sorting.
|
||||
:param sort_dir: Optional, direction of sorting, either 'asc' (the
|
||||
default) or 'desc'.
|
||||
:returns: A list of nodes.
|
||||
"""
|
||||
return self.admin_clients("ironic").node.list(
|
||||
associated=associated, maintenance=maintenance, marker=marker,
|
||||
limit=limit, detail=detail, sort_key=sort_key, sort_dir=sort_dir)
|
||||
|
||||
@base.atomic_action_timer("ironic.delete_node")
|
||||
def _delete_node(self, node_id):
|
||||
"""Delete the node with specific id.
|
||||
|
||||
:param node_id: id of the node to be deleted
|
||||
"""
|
||||
self.admin_clients("ironic").node.delete(node_id)
|
20
samples/tasks/scenarios/ironic/create-and-delete-node.json
Normal file
20
samples/tasks/scenarios/ironic/create-and-delete-node.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"IronicNodes.create_and_delete_node": [
|
||||
{
|
||||
"args": {
|
||||
"driver": "pxe_ssh"
|
||||
},
|
||||
"runner": {
|
||||
"type": "constant",
|
||||
"times": 10,
|
||||
"concurrency": 2
|
||||
},
|
||||
"context": {
|
||||
"users": {
|
||||
"tenants": 5,
|
||||
"users_per_tenant": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
13
samples/tasks/scenarios/ironic/create-and-delete-node.yaml
Normal file
13
samples/tasks/scenarios/ironic/create-and-delete-node.yaml
Normal file
@ -0,0 +1,13 @@
|
||||
---
|
||||
IronicNodes.create_and_delete_node:
|
||||
-
|
||||
args:
|
||||
driver: "pxe_ssh"
|
||||
runner:
|
||||
type: "constant"
|
||||
times: 10
|
||||
concurrency: 2
|
||||
context:
|
||||
users:
|
||||
tenants: 5
|
||||
users_per_tenant: 1
|
20
samples/tasks/scenarios/ironic/create-and-list-node.json
Normal file
20
samples/tasks/scenarios/ironic/create-and-list-node.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"IronicNodes.create_and_list_node": [
|
||||
{
|
||||
"args": {
|
||||
"driver": "pxe_ssh"
|
||||
},
|
||||
"runner": {
|
||||
"type": "constant",
|
||||
"times": 10,
|
||||
"concurrency": 2
|
||||
},
|
||||
"context": {
|
||||
"users": {
|
||||
"tenants": 5,
|
||||
"users_per_tenant": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
13
samples/tasks/scenarios/ironic/create-and-list-node.yaml
Normal file
13
samples/tasks/scenarios/ironic/create-and-list-node.yaml
Normal file
@ -0,0 +1,13 @@
|
||||
---
|
||||
IronicNodes.create_and_list_node:
|
||||
-
|
||||
args:
|
||||
driver: "pxe_ssh"
|
||||
runner:
|
||||
type: "constant"
|
||||
times: 10
|
||||
concurrency: 2
|
||||
context:
|
||||
users:
|
||||
tenants: 5
|
||||
users_per_tenant: 1
|
56
tests/unit/plugins/openstack/scenarios/ironic/test_nodes.py
Normal file
56
tests/unit/plugins/openstack/scenarios/ironic/test_nodes.py
Normal file
@ -0,0 +1,56 @@
|
||||
# Copyright 2015: Mirantis 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.ironic import nodes
|
||||
from tests.unit import test
|
||||
|
||||
|
||||
class IronicNodesTestCase(test.ScenarioTestCase):
|
||||
|
||||
def test_create_and_list_node(self):
|
||||
scenario = nodes.IronicNodes()
|
||||
scenario._create_node = mock.Mock()
|
||||
scenario._list_nodes = mock.Mock()
|
||||
fake_params = {
|
||||
"sort_dir": "foo1",
|
||||
"associated": "foo2",
|
||||
"sort_key": "foo3",
|
||||
"detail": True,
|
||||
"limit": "foo4",
|
||||
"maintenance": "foo5",
|
||||
"marker": "foo6",
|
||||
"fake_parameter1": "foo7"
|
||||
}
|
||||
scenario.create_and_list_node(**fake_params)
|
||||
|
||||
scenario._create_node.assert_called_once_with(fake_parameter1="foo7")
|
||||
scenario._list_nodes.assert_called_once_with(
|
||||
sort_dir="foo1", associated="foo2", sort_key="foo3", detail=True,
|
||||
limit="foo4", maintenance="foo5", marker="foo6")
|
||||
|
||||
def test_create_and_delete_node(self):
|
||||
fake_node = mock.Mock(uuid="fake_uuid")
|
||||
scenario = nodes.IronicNodes()
|
||||
scenario._create_node = mock.Mock(return_value=fake_node)
|
||||
scenario._delete_node = mock.Mock()
|
||||
|
||||
scenario.create_and_delete_node(fake_parameter1="fake1",
|
||||
fake_parameter2="fake2")
|
||||
scenario._create_node.assert_called_once_with(fake_parameter1="fake1",
|
||||
fake_parameter2="fake2")
|
||||
|
||||
scenario._delete_node.assert_called_once_with("fake_uuid")
|
68
tests/unit/plugins/openstack/scenarios/ironic/test_utils.py
Normal file
68
tests/unit/plugins/openstack/scenarios/ironic/test_utils.py
Normal file
@ -0,0 +1,68 @@
|
||||
# Copyright 2015: Mirantis 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.ironic import utils
|
||||
from tests.unit import test
|
||||
|
||||
IRONIC_UTILS = "rally.plugins.openstack.scenarios.ironic.utils"
|
||||
|
||||
|
||||
class IronicScenarioTestCase(test.ScenarioTestCase):
|
||||
|
||||
@mock.patch("rally.common.utils.generate_random_name")
|
||||
def test__create_node(self, mock_generate_random_name):
|
||||
mock_generate_random_name.return_value = "rally_fake_random_string"
|
||||
self.admin_clients("ironic").node.create.return_value = "fake_node"
|
||||
scenario = utils.IronicScenario()
|
||||
create_node = scenario._create_node(fake_param="foo")
|
||||
|
||||
self.assertEqual("fake_node", create_node)
|
||||
self.admin_clients("ironic").node.create.assert_called_once_with(
|
||||
fake_param="foo", name="rally_fake_random_string")
|
||||
self._test_atomic_action_timer(scenario.atomic_actions(),
|
||||
"ironic.create_node")
|
||||
|
||||
def test__delete_node(self):
|
||||
mock_node_delete = mock.Mock()
|
||||
self.admin_clients("ironic").node.delete = mock_node_delete
|
||||
scenario = utils.IronicScenario()
|
||||
scenario._delete_node("fake_id")
|
||||
|
||||
self.admin_clients("ironic").node.delete.assert_called_once_with(
|
||||
"fake_id")
|
||||
self._test_atomic_action_timer(scenario.atomic_actions(),
|
||||
"ironic.delete_node")
|
||||
|
||||
def test__list_nodes(self):
|
||||
self.admin_clients("ironic").node.list.return_value = ["fake"]
|
||||
scenario = utils.IronicScenario()
|
||||
fake_params = {
|
||||
"sort_dir": "foo1",
|
||||
"associated": "foo2",
|
||||
"sort_key": "foo3",
|
||||
"detail": True,
|
||||
"limit": "foo4",
|
||||
"maintenance": "foo5",
|
||||
"marker": "foo6"
|
||||
}
|
||||
return_nodes_list = scenario._list_nodes(**fake_params)
|
||||
self.assertEqual(["fake"], return_nodes_list)
|
||||
self.admin_clients("ironic").node.list.assert_called_once_with(
|
||||
sort_dir="foo1", associated="foo2", sort_key="foo3", detail=True,
|
||||
limit="foo4", maintenance="foo5", marker="foo6")
|
||||
self._test_atomic_action_timer(scenario.atomic_actions(),
|
||||
"ironic.list_nodes")
|
Loading…
Reference in New Issue
Block a user