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
This commit is contained in:
Valeriy Ponomaryov 2016-04-22 12:52:36 +03:00
parent 8bc64b424e
commit ff7b05094a
5 changed files with 115 additions and 22 deletions

View File

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

View File

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

View File

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

View File

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

View File

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