[Fuel] scenario for add and remove node

add node and remove node to/from environment

Change-Id: Ib7da11d8ffe6c692018a241a20ac978eb450f624
This commit is contained in:
Oleh Anufriiev 2015-05-06 17:46:21 +03:00
parent d2b123fb50
commit 093cefb18f
10 changed files with 514 additions and 4 deletions

View File

@ -20,3 +20,20 @@
sla:
failure_rate:
max: 0
FuelNodes.add_and_remove_node:
-
context:
fuel_environments:
environments: 2
release_id: 1
network_provider: "neutron"
deployment_mode: "ha_compact"
net_segment_type: "vlan"
resource_management_workers: 1
runner:
type: "serial"
times: 20
sla:
failure_rate:
max: 0

View File

@ -0,0 +1,128 @@
# 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 collections
from rally.common import broker
from rally.common.i18n import _
from rally.common import log as logging
from rally.common import utils as rutils
from rally import consts
from rally import exceptions
from rally.plugins.openstack.scenarios.fuel import utils as fuel_utils
from rally.task import context as base
LOG = logging.getLogger(__name__)
@base.configure(name="fuel_environments", order=110)
class FuelEnvGenerator(base.Context):
"""Context for generating Fuel environments."""
CONFIG_SCHEMA = {
"type": "object",
"$schema": consts.JSON_SCHEMA,
"properties": {
"environments": {
"type": "integer",
"minimum": 1
},
"release_id": {
"type": "integer"
},
"network_provider": {
"type": "string"
},
"net_segment_type": {
"type": "string"
},
"deployment_mode": {
"type": "string"
},
"resource_management_workers": {
"type": "integer",
"minimum": 1
},
},
"additionalProperties": False
}
DEFAULT_CONFIG = {
"environments": 5,
"release_id": 1,
"network_provider": "neutron",
"deployment_mode": "ha_compact",
"net_segment_type": "vlan",
"resource_management_workers": 2
}
def _create_envs(self):
threads = self.config["resource_management_workers"]
envs = collections.deque()
def publish(queue):
kwargs = {"release_id": self.config["release_id"],
"network_provider": self.config["network_provider"],
"deployment_mode": self.config["deployment_mode"],
"net_segment_type": self.config["net_segment_type"]}
for i in range(self.config["environments"]):
queue.append(kwargs)
def consume(cache, kwargs):
env_id = self.fscenario._create_environment(**kwargs)
envs.append(env_id)
broker.run(publish, consume, threads)
return list(envs)
def _delete_envs(self):
threads = self.config["resource_management_workers"]
def publish(queue):
queue.extend(self.context["fuel"]["environments"])
def consume(cache, env_id):
self.fscenario._delete_environment(env_id)
broker.run(publish, consume, threads)
self.context["fuel"] = {}
@rutils.log_task_wrapper(LOG.info, _("Enter context: `fuel_environments`"))
def setup(self):
"""Create Fuel environments, using the broker pattern."""
self.context.setdefault("fuel", {})
self.context["fuel"].setdefault("environments", [])
threads = self.config["resource_management_workers"]
LOG.debug("Creating %(envs)d environments using %(threads)s threads" %
{"envs": self.config["environments"],
"threads": threads})
self.fscenario = fuel_utils.FuelScenario(self.context)
self.context["fuel"]["environments"] = self._create_envs()
if len(self.context[
"fuel"]["environments"]) != self.config["environments"]:
raise exceptions.ContextSetupFailure(
ctx_name=self.get_name(),
msg=_("failed to create the requested"
" number of environments."))
@rutils.log_task_wrapper(LOG.info, _("Exit context: `fuel_environments`"))
def cleanup(self):
"""Delete environments, using the broker pattern."""
self._delete_envs()

View File

@ -0,0 +1,40 @@
# 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
from rally.plugins.openstack import scenario
from rally.plugins.openstack.scenarios.fuel import utils
from rally.task import validation
class FuelNodes(utils.FuelScenario):
"""Benchmark scenarios for Fuel nodes."""
@validation.required_clients("fuel", admin=True)
@validation.required_openstack(admin=True)
@validation.required_contexts("fuel_environments")
@scenario.configure()
def add_and_remove_node(self, node_roles=None):
"""Add node to environment and remove
:param node_roles: list. Roles, which node should be assigned to
env with
"""
env_id = random.choice(self.context["fuel"]["environments"])
node_id = self._get_free_node_id()
self._add_node(env_id, [node_id], node_roles)
self._remove_node(env_id, node_id)

View File

@ -14,6 +14,7 @@
import os
import random
import time
import six
@ -138,5 +139,60 @@ class FuelScenario(scenario.OpenStackScenario):
@atomic.action_timer("fuel.delete_environment")
def _delete_environment(self, env_id, retries=5):
self.admin_clients("fuel").environment.delete(
env_id, retries)
self.admin_clients("fuel").environment.delete(env_id, retries)
@atomic.action_timer("fuel.add_node")
def _add_node(self, env_id, node_ids, node_roles=None):
"""Add node to environment
:param env_id environment id
:param node_ids list of node ids
:param node_roles list of roles
"""
node_roles = node_roles or ["compute"]
try:
self.admin_clients("fuel").environment.client.add_nodes(
env_id, node_ids, node_roles)
except BaseException as e:
raise RuntimeError(
"Unable to add node(s) to environment. Fuel client exited "
"with error %s" % e)
@atomic.action_timer("fuel.delete_node")
def _remove_node(self, env_id, node_id):
env = FuelClient.fuelclient_module.objects.environment.Environment(
env_id)
try:
env.unassign([node_id])
except BaseException as e:
raise RuntimeError(
"Unable to add node(s) to environment. Fuel client exited "
"with error %s" % e)
@atomic.action_timer("fuel.list_nodes")
def _list_node_ids(self, env_id=None):
result = self.admin_clients("fuel").node.get_all(
environment_id=env_id)
return [x["id"] for x in result]
def _node_is_assigned(self, node_id):
try:
node = self.admin_clients("fuel").node.get_by_id(node_id)
return bool(node["cluster"])
except BaseException as e:
raise RuntimeError(
"Unable to add node(s) to environment. Fuel client exited "
"with error %s" % e)
def _get_free_node_id(self):
node_ids = self._list_node_ids()
random.shuffle(node_ids)
for node_id in node_ids:
if not self._node_is_assigned(node_id):
return node_id
else:
raise RuntimeError("Can not found free node.")

View File

@ -0,0 +1,24 @@
{
"FuelNodes.add_and_remove_node": [
{
"args": {
"node_roles": ["compute"]
},
"context": {
"fuel_environments": {
"environments": 5,
"release_id": 1,
"network_provider": "neutron",
"deployment_mode": "ha_compact",
"net_segment_type": "vlan",
"resource_management_workers": 2
}
},
"runner": {
"type": "constant",
"times": 200,
"concurrency": 10
}
}
]
}

View File

@ -0,0 +1,17 @@
---
FuelNodes.add_and_remove_node:
-
args:
node_roles: ["compute"]
context:
fuel_environments:
environments: 5
release_id: 1
network_provider: "neutron"
deployment_mode: "ha_compact"
net_segment_type: "vlan"
resource_management_workers: 2
runner:
type: "constant"
times: 200
concurrency: 10

View File

@ -602,12 +602,13 @@ class FuelEnvironmentTestCase(test.TestCase):
@mock.patch("%s.FuelEnvironment._manager" % BASE)
def test_is_deleted(self, mock__manager):
mock__manager.return_value.get.return_value = []
mock__manager.return_value.get.return_value = None
fres = resources.FuelEnvironment()
fres.id = mock.Mock()
self.assertTrue(fres.is_deleted())
mock__manager.return_value.get.return_value = ["env"]
mock__manager.return_value.get.return_value = "env"
self.assertFalse(fres.is_deleted())
mock__manager.return_value.get.assert_called_with(fres.id.return_value)
@mock.patch("%s.FuelEnvironment._manager" % BASE)
def test_list(self, mock__manager):

View File

@ -0,0 +1,108 @@
# 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 import exceptions
from rally.plugins.openstack.context import fuel
from tests.unit import test
BASE = "rally.plugins.openstack.context.fuel"
class FuelEnvGeneratorTestCase(test.TestCase):
@mock.patch(BASE + ".FuelEnvGenerator._create_envs",
return_value=["env1"])
@mock.patch(BASE + ".fuel_utils.FuelScenario")
def test_setup(self, mock_fuel_scenario, mock__create_envs):
context = {}
context["config"] = {"fuel_environments": {"environments": 1}}
context["task"] = {"uuid": "some_uuid"}
context["admin"] = {"endpoint": "some_endpoint"}
env_ctx = fuel.FuelEnvGenerator(context)
env_ctx.setup()
self.assertIn("fuel", env_ctx.context)
self.assertIn("environments", env_ctx.context["fuel"])
mock__create_envs.assert_called_once_with()
mock_fuel_scenario.assert_called_once_with(context)
@mock.patch(BASE + ".FuelEnvGenerator._create_envs",
return_value=["env1"])
@mock.patch(BASE + ".fuel_utils.FuelScenario")
def test_setup_error(self, mock_fuel_scenario, mock__create_envs):
context = {}
context["config"] = {"fuel_environments": {"environments": 5}}
context["task"] = {"uuid": "some_uuid"}
context["admin"] = {"endpoint": "some_endpoint"}
env_ctx = fuel.FuelEnvGenerator(context)
self.assertRaises(exceptions.ContextSetupFailure, env_ctx.setup)
def test__create_envs(self):
config = {"environments": 4,
"release_id": 42,
"network_provider": "provider",
"deployment_mode": "mode",
"net_segment_type": "type",
"resource_management_workers": 3}
context = {"task": {},
"config": {"fuel_environments": config}}
env_ctx = fuel.FuelEnvGenerator(context)
env_ctx.fscenario = mock.Mock()
env_ctx.fscenario.return_value._create_environment.return_value = "id"
self.assertEqual(config["environments"], len(env_ctx._create_envs()))
enves = config.pop("environments")
config.pop("resource_management_workers")
exp_calls = [mock.call(**config) for i in range(enves)]
self.assertEqual(
exp_calls,
env_ctx.fscenario._create_environment.mock_calls)
def test__delete_envs(self):
config = {"release_id": 42,
"network_provider": "provider",
"deployment_mode": "mode",
"net_segment_type": "type",
"resource_management_workers": 3}
context = {"task": {},
"config": {"fuel_environments": config},
"fuel": {"environments": ["id", "id", "id"]}}
env_ctx = fuel.FuelEnvGenerator(context)
env_ctx.fscenario = mock.Mock()
env_ctx._delete_envs()
self.assertEqual({}, context["fuel"])
def test_cleanup(self):
config = {"release_id": 42,
"network_provider": "provider",
"deployment_mode": "mode",
"net_segment_type": "type",
"resource_management_workers": 3}
context = {"task": {"uuid": "some_id"},
"config": {"fuel_environments": config},
"fuel": {"environments": ["id", "id", "id"]}}
env_ctx = fuel.FuelEnvGenerator(context)
env_ctx._delete_envs = mock.Mock()
env_ctx.cleanup()
env_ctx._delete_envs.assert_called_once_with()

View File

@ -0,0 +1,48 @@
# 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.fuel import nodes
from tests.unit import test
class FuelNodesTestCase(test.TestCase):
context = {"fuel": {"environments": ["1"]}}
def test_add_and_remove_node(self):
scenario = nodes.FuelNodes(self.context)
scenario._list_node_ids = mock.Mock(return_value=["1"])
scenario._node_is_assigned = mock.Mock(return_value=False)
scenario._add_node = mock.Mock()
scenario._remove_node = mock.Mock()
scenario.add_and_remove_node(node_roles="some_role")
scenario._list_node_ids.assert_called_once_with()
scenario._node_is_assigned.assert_called_once_with("1")
scenario._add_node.assert_called_once_with("1", ["1"], "some_role")
scenario._remove_node.assert_called_once_with("1", "1")
def test_add_and_remove_nodes_error(self):
scenario = nodes.FuelNodes(self.context)
scenario._list_node_ids = mock.Mock(return_value=["1"])
scenario._node_is_assigned = mock.Mock(return_value=True)
scenario._add_node = mock.Mock()
scenario._remove_node = mock.Mock()
self.assertRaises(RuntimeError,
scenario.add_and_remove_node,
node_roles="some_role")

View File

@ -190,7 +190,78 @@ class FuelScenarioTestCase(test.ScenarioTestCase):
def test__delete_environment(self):
fuel_scenario = utils.FuelScenario()
fuel_scenario.admin_clients = self.admin_clients
fuel_scenario._delete_environment(42, 33)
tmp_mock = fuel_scenario.admin_clients("fuel")
tmp_mock.environment.delete.assert_called_once_with(42, 33)
def test__add_nodes(self):
fscen = utils.FuelScenario()
fscen.admin_clients = mock.Mock()
fscen._add_node("1", ["42"], node_roles=["some_role"])
tmp_mock = fscen.admin_clients.return_value.environment.client
tmp_mock.add_nodes.assert_called_once_with("1", ["42"], ["some_role"])
def test__add_nodes_error(self):
fscen = utils.FuelScenario()
fscen.admin_clients = mock.Mock()
tmp_mock = fscen.admin_clients.return_value.environment.client
tmp_mock.add_nodes.side_effect = BaseException
self.assertRaises(RuntimeError, fscen._add_node, "1", "42",
node_roles="some_role")
@mock.patch(UTILS + "FuelClient")
def test__remove_nodes(self, mock_fuel_client):
mock_tmp = mock_fuel_client.fuelclient_module.objects
mock_env = mock_tmp.environment.Environment
mock_env.return_value = mock.Mock()
fscen = utils.FuelScenario()
fscen._remove_node("1", "2")
mock_env.assert_called_once_with("1")
mock_env.return_value.unassign.assert_called_once_with(["2"])
@mock.patch(UTILS + "FuelClient")
def test__remove_nodes_error(self, mock_fuel_client):
mock_tmp = mock_fuel_client.fuelclient_module.objects
mock_env = mock_tmp.environment.Environment
mock_env.return_value = mock.Mock()
mock_env.return_value.unassign.side_effect = BaseException
fscen = utils.FuelScenario()
self.assertRaises(RuntimeError, fscen._remove_node, "1", "2")
def test__list_node_ids(self):
fscen = utils.FuelScenario()
fscen.admin_clients = mock.Mock()
fscen.admin_clients.return_value.node.get_all.return_value = [
{"id": "id1"}, {"id": "id2"}]
res = fscen._list_node_ids("env")
self.assertEqual(["id1", "id2"], res)
tmp_mock = fscen.admin_clients.return_value.node.get_all
tmp_mock.assert_called_once_with(environment_id="env")
def test__node_is_assigned(self):
fscen = utils.FuelScenario()
fscen.admin_clients = mock.Mock()
fscen.admin_clients.return_value.node.get_by_id.return_value = {
"id": "id1", "cluster": "some_id"}
self.assertTrue(fscen._node_is_assigned("id1"))
fscen.admin_clients.return_value.node.get_by_id.return_value[
"cluster"] = ""
self.assertFalse(fscen._node_is_assigned("id2"))
@mock.patch(UTILS + "FuelScenario._node_is_assigned", return_value=False)
@mock.patch(UTILS + "FuelScenario._list_node_ids",
return_value=["id1", "id2"])
def test__get_free_node_id(self, mock__list_node_ids,
mock__node_is_assigned):
node_id = utils.FuelScenario()._get_free_node_id()
self.assertIn(node_id, mock__list_node_ids.return_value)
@mock.patch(UTILS + "FuelScenario._node_is_assigned", return_value=True)
@mock.patch(UTILS + "FuelScenario._list_node_ids",
return_value=["id1", "id2"])
def test__get_free_node_id_exception(self, mock__list_node_ids,
mock__node_is_assigned):
self.assertRaises(RuntimeError,
utils.FuelScenario()._get_free_node_id)