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:
Roman Vasilets 2015-05-27 19:13:58 +03:00
parent e736a18ced
commit 8dd702c904
12 changed files with 392 additions and 1 deletions

View 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 %}

View File

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

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

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

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

View 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

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

View 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

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

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