From 50fa2eb6531f15815ef330c09ec4858d2cd0fe5e Mon Sep 17 00:00:00 2001 From: Valeriy Ponomaryov Date: Fri, 22 Apr 2016 12:52:36 +0300 Subject: [PATCH] Add possibility to balance usage of users For the moment all users for tasks are taken randomly and there is no way to balance them between tasks. It may be very useful when we have difference between first usage of tenant/user and all consecutive. In this case we get different load results. Therefore, add enum config option 'user_choice_method' to 'users' context that defines approach for picking up users. Two values are available: - random - round_robin Default one is compatible with old approach - "random". Also, update one of scenarios to use "round_robin" approach to make sure it works. Change-Id: I26fb090eb89c22f5d50529cb73b6ed54fc3d7e15 --- rally-jobs/rally-manila.yaml | 7 +- .../openstack/context/keystone/users.py | 39 +++++++++- .../tasks/scenarios/manila/list-shares.json | 7 +- .../tasks/scenarios/manila/list-shares.yaml | 7 +- .../openstack/context/keystone/test_users.py | 77 ++++++++++++++++--- 5 files changed, 115 insertions(+), 22 deletions(-) diff --git a/rally-jobs/rally-manila.yaml b/rally-jobs/rally-manila.yaml index b489ed25..09a1bb16 100644 --- a/rally-jobs/rally-manila.yaml +++ b/rally-jobs/rally-manila.yaml @@ -23,12 +23,13 @@ detailed: True runner: type: "constant" - times: 10 + times: 12 concurrency: 1 context: users: - tenants: 1 - users_per_tenant: 1 + tenants: 3 + users_per_tenant: 4 + user_choice_method: "round_robin" sla: failure_rate: max: 0 diff --git a/rally/plugins/openstack/context/keystone/users.py b/rally/plugins/openstack/context/keystone/users.py index 1765b496..f7fbab6c 100644 --- a/rally/plugins/openstack/context/keystone/users.py +++ b/rally/plugins/openstack/context/keystone/users.py @@ -56,6 +56,20 @@ CONF.register_opts(USER_CONTEXT_OPTS, class UserContextMixin(object): + @property + def user_choice_method(self): + if not hasattr(self, "_user_choice_method"): + self._user_choice_method = self.context["config"].get( + "users", {}).get("user_choice_method") + if self._user_choice_method is None: + # NOTE(vponomaryov): consider 'existing_users' context + # picking up value for 'user_choice_method' + # when it is supported there. + # Until it happens we use old "random" approach for + # 'existing_users' context. + self.user_choice_method = "random" + return self._user_choice_method + def map_for_scenario(self, context_obj): """Pass only context of one user and related to it tenant to scenario. @@ -67,8 +81,19 @@ class UserContextMixin(object): if key not in ["users", "tenants"]: scenario_ctx[key] = value - user = random.choice(context_obj["users"]) - tenant = context_obj["tenants"][user["tenant_id"]] + if self.user_choice_method == "random": + user = random.choice(context_obj["users"]) + tenant = context_obj["tenants"][user["tenant_id"]] + else: + # Second and last case - 'round_robin'. + tenants_amount = len(context_obj["tenants"]) + tenant_id = sorted(context_obj["tenants"].keys())[ + context_obj["iteration"] % tenants_amount] + tenant = context_obj["tenants"][tenant_id] + users = context_obj["tenants"][tenant_id]["users"] + user = users[ + int(context_obj["iteration"] / tenants_amount) % len(users)] + scenario_ctx["user"], scenario_ctx["tenant"] = user, tenant return scenario_ctx @@ -100,6 +125,9 @@ class UserGenerator(UserContextMixin, context.Context): "user_domain": { "type": "string", }, + "user_choice_method": { + "enum": ["random", "round_robin"], + }, }, "additionalProperties": False } @@ -110,7 +138,8 @@ class UserGenerator(UserContextMixin, context.Context): "resource_management_workers": cfg.CONF.users_context.resource_management_workers, "project_domain": cfg.CONF.users_context.project_domain, - "user_domain": cfg.CONF.users_context.user_domain + "user_domain": cfg.CONF.users_context.user_domain, + "user_choice_method": "random", } def __init__(self, context): @@ -179,7 +208,7 @@ class UserGenerator(UserContextMixin, context.Context): cache["client"] = keystone.wrap(clients.keystone()) tenant = cache["client"].create_project( self.generate_random_name(), domain) - tenant_dict = {"id": tenant.id, "name": tenant.name} + tenant_dict = {"id": tenant.id, "name": tenant.name, "users": []} tenants.append(tenant_dict) # NOTE(msdubov): consume() will fill the tenants list in the closure. @@ -284,6 +313,8 @@ class UserGenerator(UserContextMixin, context.Context): LOG.debug("Creating %(users)d users using %(threads)s threads" % {"users": users_num, "threads": threads}) self.context["users"] = self._create_users() + for user in self.context["users"]: + self.context["tenants"][user["tenant_id"]]["users"].append(user) if len(self.context["users"]) < users_num: raise exceptions.ContextSetupFailure( diff --git a/samples/tasks/scenarios/manila/list-shares.json b/samples/tasks/scenarios/manila/list-shares.json index 0afb645d..f87d8412 100644 --- a/samples/tasks/scenarios/manila/list-shares.json +++ b/samples/tasks/scenarios/manila/list-shares.json @@ -6,13 +6,14 @@ }, "runner": { "type": "constant", - "times": 10, + "times": 12, "concurrency": 1 }, "context": { "users": { - "tenants": 1, - "users_per_tenant": 1 + "tenants": 3, + "users_per_tenant": 4, + "user_choice_method": "round_robin" } } } diff --git a/samples/tasks/scenarios/manila/list-shares.yaml b/samples/tasks/scenarios/manila/list-shares.yaml index 0b89b86d..658da16d 100644 --- a/samples/tasks/scenarios/manila/list-shares.yaml +++ b/samples/tasks/scenarios/manila/list-shares.yaml @@ -5,9 +5,10 @@ detailed: True runner: type: "constant" - times: 10 + times: 12 concurrency: 1 context: users: - tenants: 1 - users_per_tenant: 1 + tenants: 3 + users_per_tenant: 4 + user_choice_method: "round_robin" diff --git a/tests/unit/plugins/openstack/context/keystone/test_users.py b/tests/unit/plugins/openstack/context/keystone/test_users.py index c76872d3..c95686ef 100644 --- a/tests/unit/plugins/openstack/context/keystone/test_users.py +++ b/tests/unit/plugins/openstack/context/keystone/test_users.py @@ -26,16 +26,34 @@ CTX = "rally.plugins.openstack.context.keystone.users" class UserContextMixinTestCase(test.TestCase): + def setUp(self): + super(self.__class__, self).setUp() + self.mixin = users.UserContextMixin() + self.mixin.context = { + "config": { + "users": { + "user_choice_method": "random", + }, + }, + } + @mock.patch("%s.random.choice" % CTX, side_effect=lambda x: x[1]) - def test_map_for_scenario(self, mock_choice): + def test_map_for_scenario_random(self, mock_choice): + self.mixin.context["config"]["users"]["user_choice_method"] = ( + "random") users_ = [] tenants = {} - for i in range(2): - tenants[str(i)] = {"name": str(i)} - for j in range(3): - users_.append({"id": "%s_%s" % (i, j), - "tenant_id": str(i), "credential": "credential"}) + for i in ("0", "1"): + tenants[i] = {"id": i, "name": i, "users": []} + for j in ("a", "b", "c"): + user = { + "id": "%s_%s" % (i, j), + "tenant_id": i, + "credential": "credential", + } + users_.append(user) + tenants[i]["users"].append(user) context = { "admin": mock.MagicMock(), @@ -44,21 +62,62 @@ class UserContextMixinTestCase(test.TestCase): "some_random_key": { "nested": mock.MagicMock(), "one_more": 10 - } + }, + "config": { + "users": { + "user_choice_method": "random", + }, + }, } chosen_tenant = context["tenants"][context["users"][1]["tenant_id"]] expected_context = { "admin": context["admin"], "user": context["users"][1], "tenant": chosen_tenant, - "some_random_key": context["some_random_key"] + "some_random_key": context["some_random_key"], + "config": context["config"] } self.assertEqual( expected_context, - users.UserContextMixin().map_for_scenario(context) + self.mixin.map_for_scenario(context) ) + @mock.patch("%s.random.choice" % CTX, + side_effect=Exception("Should not be raised")) + def test_map_for_scenario_round_robin(self, mock_choice): + self.mixin.context["config"]["users"]["user_choice_method"] = ( + "round_robin") + tenants = {s: {"name": s, "users": []} for s in ("0", "1")} + users_ = [] + for tenant_id, tenant in tenants.items(): + for i in ("0", "1"): + user = {"id": "%s_%s" % (tenant_id, i), "tenant_id": tenant_id, + "endpoint": "endpoint"} + users_.append(user) + tenant["users"].append(user) + context = { + "admin": mock.MagicMock(), + "users": users_, + "tenants": tenants, + "some_random_key": { + "nested": mock.MagicMock(), + "one_more": 10 + }, + "config": { + "users": { + "user_choice_method": "round_robin", + }, + }, + } + expected_ids = ["0_0", "1_0", "0_1", "1_1"] * 4 + mapped_ids = [] + for i in range(16): + context["iteration"] = i + user = self.mixin.map_for_scenario(context) + mapped_ids.append(user["user"]["id"]) + self.assertEqual(expected_ids, mapped_ids) + class UserGeneratorTestCase(test.ScenarioTestCase):