diff --git a/rally-jobs/rally-manila-no-ss.yaml b/rally-jobs/rally-manila-no-ss.yaml index a398ec9a..1502e2d9 100644 --- a/rally-jobs/rally-manila-no-ss.yaml +++ b/rally-jobs/rally-manila-no-ss.yaml @@ -58,3 +58,35 @@ failure_rate: max: 0 {% endfor %} + + + ManilaShares.set_and_delete_metadata: + - + args: + sets: 1 + set_size: 3 + delete_size: 3 + key_min_length: 1 + key_max_length: 256 + value_min_length: 1 + value_max_length: 1024 + runner: + type: "constant" + times: 10 + concurrency: 10 + context: + quotas: + manila: + shares: -1 + gigabytes: -1 + users: + tenants: 1 + users_per_tenant: 1 + manila_shares: + shares_per_tenant: 1 + share_proto: "NFS" + size: 1 + share_type: "dhss_false" + sla: + failure_rate: + max: 0 diff --git a/rally-jobs/rally-manila.yaml b/rally-jobs/rally-manila.yaml index 66c259a8..e29e6373 100644 --- a/rally-jobs/rally-manila.yaml +++ b/rally-jobs/rally-manila.yaml @@ -162,3 +162,37 @@ failure_rate: max: 0 {% endfor %} + + ManilaShares.set_and_delete_metadata: + - + args: + sets: 1 + set_size: 3 + delete_size: 3 + key_min_length: 1 + key_max_length: 256 + value_min_length: 1 + value_max_length: 1024 + runner: + type: "constant" + times: 10 + concurrency: 10 + context: + quotas: + manila: + shares: -1 + gigabytes: -1 + share_networks: -1 + users: + tenants: 1 + users_per_tenant: 1 + manila_share_networks: + use_share_networks: True + manila_shares: + shares_per_tenant: 1 + share_proto: "NFS" + size: 1 + share_type: "dhss_true" + sla: + failure_rate: + max: 0 diff --git a/rally/plugins/openstack/context/manila/consts.py b/rally/plugins/openstack/context/manila/consts.py index d3c62bc4..f38db74b 100644 --- a/rally/plugins/openstack/context/manila/consts.py +++ b/rally/plugins/openstack/context/manila/consts.py @@ -13,5 +13,6 @@ # License for the specific language governing permissions and limitations # under the License. +SHARES_CONTEXT_NAME = "manila_shares" SHARE_NETWORKS_CONTEXT_NAME = "manila_share_networks" SECURITY_SERVICES_CONTEXT_NAME = "manila_security_services" diff --git a/rally/plugins/openstack/context/manila/manila_share_networks.py b/rally/plugins/openstack/context/manila/manila_share_networks.py index 82c156de..31687494 100644 --- a/rally/plugins/openstack/context/manila/manila_share_networks.py +++ b/rally/plugins/openstack/context/manila/manila_share_networks.py @@ -33,7 +33,7 @@ CONTEXT_NAME = consts.SHARE_NETWORKS_CONTEXT_NAME @context.configure(name=CONTEXT_NAME, order=450) class ShareNetworks(context.Context): - """This context creates resources specific for Manila project.""" + """This context creates share networks for Manila project.""" CONFIG_SCHEMA = { "type": "object", "$schema": rally_consts.JSON_SCHEMA, diff --git a/rally/plugins/openstack/context/manila/manila_shares.py b/rally/plugins/openstack/context/manila/manila_shares.py new file mode 100644 index 00000000..86d39d87 --- /dev/null +++ b/rally/plugins/openstack/context/manila/manila_shares.py @@ -0,0 +1,107 @@ +# Copyright 2016 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 oslo_config import cfg + +from rally.common.i18n import _ +from rally.common import logging +from rally.common import utils +from rally import consts as rally_consts +from rally.plugins.openstack.cleanup import manager as resource_manager +from rally.plugins.openstack.context.manila import consts +from rally.plugins.openstack.scenarios.manila import utils as manila_utils +from rally.task import context + +CONF = cfg.CONF +LOG = logging.getLogger(__name__) + +CONTEXT_NAME = consts.SHARES_CONTEXT_NAME + + +@context.configure(name=CONTEXT_NAME, order=455) +class Shares(context.Context): + """This context creates shares for Manila project.""" + + CONFIG_SCHEMA = { + "type": "object", + "$schema": rally_consts.JSON_SCHEMA, + "properties": { + "shares_per_tenant": { + "type": "integer", + "minimum": 1, + }, + "size": { + "type": "integer", + "minimum": 1 + }, + "share_proto": { + "type": "string", + }, + "share_type": { + "type": "string", + }, + }, + "additionalProperties": False + } + + DEFAULT_CONFIG = { + "shares_per_tenant": 1, + "size": 1, + "share_proto": "NFS", + "share_type": None, + } + + def _create_shares(self, manila_scenario, tenant_id, share_proto, size=1, + share_type=None): + tenant_ctxt = self.context["tenants"][tenant_id] + tenant_ctxt.setdefault("shares", []) + for i in range(self.config["shares_per_tenant"]): + kwargs = {"share_proto": share_proto, "size": size} + if share_type: + kwargs["share_type"] = share_type + share_networks = tenant_ctxt.get("manila_share_networks", {}).get( + "share_networks", []) + if share_networks: + kwargs["share_network"] = share_networks[ + i % len(share_networks)]["id"] + share = manila_scenario._create_share(**kwargs) + tenant_ctxt["shares"].append(share.to_dict()) + + @logging.log_task_wrapper( + LOG.info, _("Enter context: `%s`") % CONTEXT_NAME) + def setup(self): + for user, tenant_id in ( + utils.iterate_per_tenants(self.context.get("users", []))): + manila_scenario = manila_utils.ManilaScenario({ + "task": self.task, + "user": user, + "config": { + "api_versions": self.context["config"].get( + "api_versions", [])} + }) + self._create_shares( + manila_scenario, + tenant_id, + self.config["share_proto"], + self.config["size"], + self.config["share_type"], + ) + + @logging.log_task_wrapper(LOG.info, _("Exit context: `%s`") % CONTEXT_NAME) + def cleanup(self): + resource_manager.cleanup( + names=["manila.shares"], + users=self.context.get("users", []), + ) diff --git a/rally/plugins/openstack/scenarios/manila/shares.py b/rally/plugins/openstack/scenarios/manila/shares.py index add24aa9..95a08e49 100644 --- a/rally/plugins/openstack/scenarios/manila/shares.py +++ b/rally/plugins/openstack/scenarios/manila/shares.py @@ -15,6 +15,7 @@ from rally.common import logging from rally import consts +from rally.plugins.openstack.context.manila import consts as manila_consts from rally.plugins.openstack import scenario from rally.plugins.openstack.scenarios.manila import utils from rally.task import validation @@ -228,3 +229,53 @@ class CreateAndListShare(utils.ManilaScenario): self._create_share(share_proto=share_proto, size=size, **kwargs) self.sleep_between(min_sleep, max_sleep) self._list_shares(detailed=detailed) + + +@validation.number("sets", minval=1, integer_only=True) +@validation.number("set_size", minval=1, integer_only=True) +@validation.number("key_min_length", minval=1, maxval=256, integer_only=True) +@validation.number("key_max_length", minval=1, maxval=256, integer_only=True) +@validation.number( + "value_min_length", minval=1, maxval=1024, integer_only=True) +@validation.number( + "value_max_length", minval=1, maxval=1024, integer_only=True) +@validation.required_services(consts.Service.MANILA) +@validation.required_openstack(users=True) +@validation.required_contexts(manila_consts.SHARES_CONTEXT_NAME) +@scenario.configure( + context={"cleanup": ["manila"]}, + name="ManilaShares.set_and_delete_metadata") +class SetAndDeleteMetadata(utils.ManilaScenario): + + def run(self, sets=10, set_size=3, delete_size=3, + key_min_length=1, key_max_length=256, + value_min_length=1, value_max_length=1024): + """Sets and deletes share metadata. + + This requires a share to be created with the shares + context. Additionally, ``sets * set_size`` must be greater + than or equal to ``deletes * delete_size``. + + :param sets: how many set_metadata operations to perform + :param set_size: number of metadata keys to set in each + set_metadata operation + :param delete_size: number of metadata keys to delete in each + delete_metadata operation + :param key_min_length: minimal size of metadata key to set + :param key_max_length: maximum size of metadata key to set + :param value_min_length: minimal size of metadata value to set + :param value_max_length: maximum size of metadata value to set + """ + shares = self.context.get("tenant", {}).get("shares", []) + share = shares[self.context["iteration"] % len(shares)] + + keys = self._set_metadata( + share=share, + sets=sets, + set_size=set_size, + key_min_length=key_min_length, + key_max_length=key_max_length, + value_min_length=value_min_length, + value_max_length=value_max_length) + + self._delete_metadata(share=share, keys=keys, delete_size=delete_size) diff --git a/rally/plugins/openstack/scenarios/manila/utils.py b/rally/plugins/openstack/scenarios/manila/utils.py index a17597c4..4f34af23 100644 --- a/rally/plugins/openstack/scenarios/manila/utils.py +++ b/rally/plugins/openstack/scenarios/manila/utils.py @@ -13,9 +13,11 @@ # License for the specific language governing permissions and limitations # under the License. +import random from oslo_config import cfg +from rally import exceptions from rally.plugins.openstack.context.manila import consts from rally.plugins.openstack import scenario from rally.task import atomic @@ -244,3 +246,65 @@ class ManilaScenario(scenario.OpenStackScenario): "manila").share_networks.add_security_service( share_network, security_service) return share_network + + @atomic.action_timer("manila.set_metadata") + def _set_metadata(self, share, sets=1, set_size=1, + key_min_length=1, key_max_length=256, + value_min_length=1, value_max_length=1024): + """Sets share metadata. + + :param share: the share to set metadata on + :param sets: how many operations to perform + :param set_size: number of metadata keys to set in each operation + :param key_min_length: minimal size of metadata key to set + :param key_max_length: maximum size of metadata key to set + :param value_min_length: minimal size of metadata value to set + :param value_max_length: maximum size of metadata value to set + :returns: A list of keys that were set + :raises exceptions.InvalidArgumentsException: if invalid arguments + were provided. + """ + if not (key_min_length <= key_max_length and + value_min_length <= value_max_length): + raise exceptions.InvalidArgumentsException( + "Min length for keys and values of metadata can not be bigger " + "than maximum length.") + + keys = [] + for i in range(sets): + metadata = {} + for j in range(set_size): + if key_min_length == key_max_length: + key_length = key_min_length + else: + key_length = random.choice( + range(key_min_length, key_max_length)) + if value_min_length == value_max_length: + value_length = value_min_length + else: + value_length = random.choice( + range(value_min_length, value_max_length)) + key = self._generate_random_part(length=key_length) + keys.append(key) + metadata[key] = self._generate_random_part(length=value_length) + self.clients("manila").shares.set_metadata(share["id"], metadata) + + return keys + + @atomic.action_timer("manila.delete_metadata") + def _delete_metadata(self, share, keys, delete_size=3): + """Deletes share metadata. + + :param share: The share to delete metadata from. + :param delete_size: number of metadata keys to delete using one single + call. + :param keys: a list or tuple of keys to choose deletion candidates from + :raises exceptions.InvalidArgumentsException: if invalid arguments + were provided. + """ + if not (isinstance(keys, list) and keys): + raise exceptions.InvalidArgumentsException( + "Param 'keys' should be non-empty 'list'. keys = '%s'" % keys) + for i in range(0, len(keys), delete_size): + self.clients("manila").shares.delete_metadata( + share["id"], keys[i:i + delete_size]) diff --git a/samples/tasks/scenarios/manila/create-share-with-autocreated-share-networks-and-set-metadata.json b/samples/tasks/scenarios/manila/create-share-with-autocreated-share-networks-and-set-metadata.json new file mode 100644 index 00000000..ae9840e5 --- /dev/null +++ b/samples/tasks/scenarios/manila/create-share-with-autocreated-share-networks-and-set-metadata.json @@ -0,0 +1,51 @@ +{ + "ManilaShares.set_and_delete_metadata": [ + { + "args": { + "sets": 1, + "set_size": 3, + "delete_size": 3, + "key_min_length": 1, + "key_max_length": 256, + "value_min_length": 1, + "value_max_length": 1024 + }, + "runner": { + "type": "constant", + "times": 1, + "concurrency": 1 + }, + "context": { + "quotas": { + "manila": { + "shares": -1, + "gigabytes": -1, + "share_networks": -1 + } + }, + "users": { + "tenants": 1, + "users_per_tenant": 1, + "user_choice_method": "round_robin" + }, + "network": { + "networks_per_tenant": 1, + "start_cidr": "99.0.0.0/24" + }, + "manila_share_networks": { + "use_share_networks": true + }, + "manila_shares": { + "shares_per_tenant": 1, + "share_proto": "NFS", + "size": 1 + } + }, + "sla": { + "failure_rate": { + "max": 0 + } + } + } + ] +} diff --git a/samples/tasks/scenarios/manila/create-share-with-autocreated-share-networks-and-set-metadata.yaml b/samples/tasks/scenarios/manila/create-share-with-autocreated-share-networks-and-set-metadata.yaml new file mode 100644 index 00000000..b632230b --- /dev/null +++ b/samples/tasks/scenarios/manila/create-share-with-autocreated-share-networks-and-set-metadata.yaml @@ -0,0 +1,37 @@ +--- + ManilaShares.set_and_delete_metadata: + - + args: + sets: 1 + set_size: 3 + delete_size: 3 + key_min_length: 1 + key_max_length: 256 + value_min_length: 1 + value_max_length: 1024 + runner: + type: "constant" + times: 1 + concurrency: 1 + context: + quotas: + manila: + shares: -1 + gigabytes: -1 + share_networks: -1 + users: + tenants: 1 + users_per_tenant: 1 + user_choice_method: "round_robin" + network: + networks_per_tenant: 1 + start_cidr: "99.0.0.0/24" + manila_share_networks: + use_share_networks: True + manila_shares: + shares_per_tenant: 1 + share_proto: "NFS" + size: 1 + sla: + failure_rate: + max: 0 diff --git a/samples/tasks/scenarios/manila/create-share-without-share-networks-and-set-metadata.json b/samples/tasks/scenarios/manila/create-share-without-share-networks-and-set-metadata.json new file mode 100644 index 00000000..c80cb619 --- /dev/null +++ b/samples/tasks/scenarios/manila/create-share-without-share-networks-and-set-metadata.json @@ -0,0 +1,44 @@ +{ + "ManilaShares.set_and_delete_metadata": [ + { + "args": { + "sets": 1, + "set_size": 3, + "delete_size": 3, + "key_min_length": 1, + "key_max_length": 256, + "value_min_length": 1, + "value_max_length": 1024 + }, + "runner": { + "type": "constant", + "times": 1, + "concurrency": 1 + }, + "context": { + "quotas": { + "manila": { + "shares": -1, + "gigabytes": -1, + "share_networks": -1 + } + }, + "users": { + "tenants": 1, + "users_per_tenant": 1, + "user_choice_method": "round_robin" + }, + "manila_shares": { + "shares_per_tenant": 1, + "share_proto": "NFS", + "size": 1 + } + }, + "sla": { + "failure_rate": { + "max": 0 + } + } + } + ] +} diff --git a/samples/tasks/scenarios/manila/create-share-without-share-networks-and-set-metadata.yaml b/samples/tasks/scenarios/manila/create-share-without-share-networks-and-set-metadata.yaml new file mode 100644 index 00000000..254ee30d --- /dev/null +++ b/samples/tasks/scenarios/manila/create-share-without-share-networks-and-set-metadata.yaml @@ -0,0 +1,32 @@ +--- + ManilaShares.set_and_delete_metadata: + - + args: + sets: 1 + set_size: 3 + delete_size: 3 + key_min_length: 1 + key_max_length: 256 + value_min_length: 1 + value_max_length: 1024 + runner: + type: "constant" + times: 1 + concurrency: 1 + context: + quotas: + manila: + shares: -1 + gigabytes: -1 + share_networks: -1 + users: + tenants: 1 + users_per_tenant: 1 + user_choice_method: "round_robin" + manila_shares: + shares_per_tenant: 1 + share_proto: "NFS" + size: 1 + sla: + failure_rate: + max: 0 diff --git a/tests/unit/plugins/openstack/context/manila/test_manila_shares.py b/tests/unit/plugins/openstack/context/manila/test_manila_shares.py new file mode 100644 index 00000000..3fcd9c06 --- /dev/null +++ b/tests/unit/plugins/openstack/context/manila/test_manila_shares.py @@ -0,0 +1,202 @@ +# Copyright 2016 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 copy + +import ddt +import mock +import six + +from rally import consts as rally_consts +from rally.plugins.openstack.context.manila import consts +from rally.plugins.openstack.context.manila import manila_shares +from tests.unit import test + +MANILA_UTILS_PATH = ( + "rally.plugins.openstack.scenarios.manila.utils.ManilaScenario.") + + +class Fake(object): + def __init__(self, **kwargs): + for k, v in kwargs.items(): + setattr(self, k, v) + + def __getitem__(self, item): + return getattr(self, item) + + def to_dict(self): + return self.__dict__ + + +@ddt.ddt +class SharesTestCase(test.TestCase): + TENANTS_AMOUNT = 3 + USERS_PER_TENANT = 4 + SHARES_PER_TENANT = 7 + SHARE_NETWORKS = [{"id": "sn_%s_id" % d} for d in range(3)] + + def _get_context(self, use_share_networks=False, shares_per_tenant=None, + share_size=1, share_proto="fake_proto", share_type=None): + tenants = {} + for t_id in range(self.TENANTS_AMOUNT): + tenants[six.text_type(t_id)] = {"name": six.text_type(t_id)} + users = [] + for t_id in sorted(list(tenants.keys())): + for i in range(self.USERS_PER_TENANT): + users.append( + {"id": i, "tenant_id": t_id, "credential": "fake"}) + context = { + "config": { + "users": { + "tenants": self.TENANTS_AMOUNT, + "users_per_tenant": self.USERS_PER_TENANT, + "user_choice_method": "round_robin", + }, + consts.SHARE_NETWORKS_CONTEXT_NAME: { + "use_share_networks": use_share_networks, + "share_networks": self.SHARE_NETWORKS, + }, + consts.SHARES_CONTEXT_NAME: { + "shares_per_tenant": ( + shares_per_tenant or self.SHARES_PER_TENANT), + "size": share_size, + "share_proto": share_proto, + "share_type": share_type, + }, + }, + "admin": { + "credential": mock.MagicMock(), + }, + "task": mock.MagicMock(), + "users": users, + "tenants": tenants, + } + if use_share_networks: + for t in context["tenants"].keys(): + context["tenants"][t][consts.SHARE_NETWORKS_CONTEXT_NAME] = { + "share_networks": self.SHARE_NETWORKS, + } + return context + + def test_init(self): + ctxt = { + "task": mock.MagicMock(), + "config": { + consts.SHARES_CONTEXT_NAME: {"foo": "bar"}, + "fake": {"fake_key": "fake_value"}, + }, + } + + inst = manila_shares.Shares(ctxt) + + self.assertEqual( + {"foo": "bar", "shares_per_tenant": 1, "size": 1, + "share_proto": "NFS", "share_type": None}, + inst.config) + self.assertIn( + rally_consts.JSON_SCHEMA, inst.CONFIG_SCHEMA.get("$schema")) + self.assertFalse(inst.CONFIG_SCHEMA.get("additionalProperties")) + self.assertEqual("object", inst.CONFIG_SCHEMA.get("type")) + props = inst.CONFIG_SCHEMA.get("properties", {}) + self.assertEqual( + {"minimum": 1, "type": "integer"}, props.get("shares_per_tenant")) + self.assertEqual({"minimum": 1, "type": "integer"}, props.get("size")) + self.assertEqual({"type": "string"}, props.get("share_proto")) + self.assertEqual({"type": "string"}, props.get("share_type")) + self.assertEqual(455, inst.get_order()) + self.assertEqual(consts.SHARES_CONTEXT_NAME, inst.get_name()) + + @mock.patch(MANILA_UTILS_PATH + "_create_share") + @ddt.data(True, False) + def test_setup( + self, + use_share_networks, + mock_manila_scenario__create_share): + share_type = "fake_share_type" + ctxt = self._get_context( + use_share_networks=use_share_networks, share_type=share_type) + inst = manila_shares.Shares(ctxt) + shares = [ + Fake(id="fake_share_id_%d" % s_id) + for s_id in range(self.TENANTS_AMOUNT * self.SHARES_PER_TENANT) + ] + mock_manila_scenario__create_share.side_effect = shares + expected_ctxt = copy.deepcopy(ctxt) + + inst.setup() + + self.assertEqual( + self.TENANTS_AMOUNT * self.SHARES_PER_TENANT, + mock_manila_scenario__create_share.call_count) + for d in range(self.TENANTS_AMOUNT): + self.assertEqual( + [ + s.to_dict() for s in shares[ + (d * self.SHARES_PER_TENANT):( + d * self.SHARES_PER_TENANT + self.SHARES_PER_TENANT + ) + ] + ], + inst.context.get("tenants", {}).get("%s" % d, {}).get("shares") + ) + self.assertEqual(expected_ctxt["task"], inst.context.get("task")) + self.assertEqual(expected_ctxt["config"], inst.context.get("config")) + self.assertEqual(expected_ctxt["users"], inst.context.get("users")) + if use_share_networks: + mock_calls = [ + mock.call( + share_proto=ctxt["config"][consts.SHARES_CONTEXT_NAME][ + "share_proto"], + size=ctxt["config"][consts.SHARES_CONTEXT_NAME]["size"], + share_type=ctxt["config"][consts.SHARES_CONTEXT_NAME][ + "share_type"], + share_network=self.SHARE_NETWORKS[ + int(t_id) % len(self.SHARE_NETWORKS)]["id"] + ) for t_id in expected_ctxt["tenants"].keys() + ] + else: + mock_calls = [ + mock.call( + share_proto=ctxt["config"][consts.SHARES_CONTEXT_NAME][ + "share_proto"], + size=ctxt["config"][consts.SHARES_CONTEXT_NAME]["size"], + share_type=ctxt["config"][consts.SHARES_CONTEXT_NAME][ + "share_type"], + ) for t_id in expected_ctxt["tenants"].keys() + ] + mock_manila_scenario__create_share.assert_has_calls( + mock_calls, any_order=True) + + @mock.patch(MANILA_UTILS_PATH + "_create_share") + @mock.patch("rally.plugins.openstack.cleanup.manager.cleanup") + def test_cleanup( + self, + mock_cleanup_manager_cleanup, + mock_manila_scenario__create_share): + ctxt = self._get_context() + inst = manila_shares.Shares(ctxt) + shares = [ + Fake(id="fake_share_id_%d" % s_id) + for s_id in range(self.TENANTS_AMOUNT * self.SHARES_PER_TENANT) + ] + mock_manila_scenario__create_share.side_effect = shares + inst.setup() + + inst.cleanup() + + mock_cleanup_manager_cleanup.assert_called_once_with( + names=["manila.shares"], + users=inst.context.get("users", []), + ) diff --git a/tests/unit/plugins/openstack/scenarios/manila/test_shares.py b/tests/unit/plugins/openstack/scenarios/manila/test_shares.py index a0dc9b2e..4dc7faa9 100644 --- a/tests/unit/plugins/openstack/scenarios/manila/test_shares.py +++ b/tests/unit/plugins/openstack/scenarios/manila/test_shares.py @@ -209,3 +209,37 @@ class ManilaSharesTestCase(test.ScenarioTestCase): scenario._create_share.assert_called_once_with(**params) scenario.sleep_between.assert_called_once_with(3, 4) scenario._list_shares.assert_called_once_with(detailed=detailed) + + @ddt.data( + ({}, 0, 0), + ({}, 1, 1), + ({}, 2, 2), + ({}, 3, 0), + ({"sets": 5, "set_size": 8, "delete_size": 10}, 1, 1), + ) + @ddt.unpack + def test_set_and_delete_metadata(self, params, iteration, share_number): + scenario = shares.SetAndDeleteMetadata() + share_list = [{"id": "fake_share_%s_id" % d} for d in range(3)] + scenario.context = {"tenant": {"shares": share_list}} + scenario.context["iteration"] = iteration + scenario._set_metadata = mock.MagicMock() + scenario._delete_metadata = mock.MagicMock() + expected_set_params = { + "share": share_list[share_number], + "sets": params.get("sets", 10), + "set_size": params.get("set_size", 3), + "key_min_length": params.get("key_min_length", 1), + "key_max_length": params.get("key_max_length", 256), + "value_min_length": params.get("value_min_length", 1), + "value_max_length": params.get("value_max_length", 1024), + } + + scenario.run(**params) + + scenario._set_metadata.assert_called_once_with(**expected_set_params) + scenario._delete_metadata.assert_called_once_with( + share=share_list[share_number], + keys=scenario._set_metadata.return_value, + delete_size=params.get("delete_size", 3), + ) diff --git a/tests/unit/plugins/openstack/scenarios/manila/test_utils.py b/tests/unit/plugins/openstack/scenarios/manila/test_utils.py index a8161fcd..546fd4ac 100644 --- a/tests/unit/plugins/openstack/scenarios/manila/test_utils.py +++ b/tests/unit/plugins/openstack/scenarios/manila/test_utils.py @@ -16,6 +16,7 @@ import ddt import mock +from rally import exceptions from rally.plugins.openstack.context.manila import consts from rally.plugins.openstack.scenarios.manila import utils from tests.unit import test @@ -41,12 +42,14 @@ class ManilaScenarioTestCase(test.ScenarioTestCase): }, "iteration": 0, } - self.scenario.generate_random_name = mock.Mock() + fake_random_name = "fake_random_name_value" + self.scenario.generate_random_name = mock.Mock( + return_value=fake_random_name) self.scenario._create_share("nfs") self.clients("manila").shares.create.assert_called_once_with( - "nfs", 1, name=self.scenario.generate_random_name.return_value, + "nfs", 1, name=fake_random_name, share_network=self.scenario.context["tenant"][ consts.SHARE_NETWORKS_CONTEXT_NAME]["share_networks"][0]["id"]) @@ -213,3 +216,87 @@ class ManilaScenarioTestCase(test.ScenarioTestCase): self.clients( "manila").share_networks.add_security_service.assert_has_calls([ mock.call(fake_sn, fake_ss)]) + + @ddt.data( + {"key_min_length": 5, "key_max_length": 4}, + {"value_min_length": 5, "value_max_length": 4}, + ) + def test__set_metadata_wrong_params(self, params): + self.assertRaises( + exceptions.InvalidArgumentsException, + self.scenario._set_metadata, + {"id": "fake_share_id"}, **params) + + @ddt.data( + {}, + {"sets": 0, "set_size": 1}, + {"sets": 1, "set_size": 1}, + {"sets": 5, "set_size": 7}, + {"sets": 5, "set_size": 2}, + {"key_min_length": 1, "key_max_length": 1}, + {"key_min_length": 1, "key_max_length": 2}, + {"key_min_length": 256, "key_max_length": 256}, + {"value_min_length": 1, "value_max_length": 1}, + {"value_min_length": 1, "value_max_length": 2}, + {"value_min_length": 1024, "value_max_length": 1024}, + ) + def test__set_metadata(self, params): + share = {"id": "fake_share_id"} + sets = params.get("sets", 1) + set_size = params.get("set_size", 1) + gen_name_calls = sets * set_size * 2 + data = range(gen_name_calls) + generator_data = iter(data) + + def fake_random_name(prefix="fake", length="fake"): + return next(generator_data) + + scenario = self.scenario + scenario.clients = mock.MagicMock() + scenario._generate_random_part = mock.MagicMock( + side_effect=fake_random_name) + + keys = scenario._set_metadata(share, **params) + + self.assertEqual( + gen_name_calls, + scenario._generate_random_part.call_count) + self.assertEqual( + params.get("sets", 1), + scenario.clients.return_value.shares.set_metadata.call_count) + scenario.clients.return_value.shares.set_metadata.assert_has_calls([ + mock.call( + share["id"], + dict([(j, j + 1) for j in data[ + i * set_size * 2: (i + 1) * set_size * 2: 2]]) + ) for i in range(sets) + ]) + self.assertEqual([i for i in range(0, gen_name_calls, 2)], keys) + + @ddt.data(None, [], {"fake_set"}, {"fake_key": "fake_value"}) + def test__delete_metadata_wrong_params(self, keys): + self.assertRaises( + exceptions.InvalidArgumentsException, + self.scenario._delete_metadata, + "fake_share", keys=keys, + ) + + @ddt.data( + {"keys": [i for i in range(30)]}, + {"keys": list(range(7)), "delete_size": 2}, + {"keys": list(range(7)), "delete_size": 3}, + {"keys": list(range(7)), "delete_size": 4}, + ) + def test__delete_metadata(self, params): + share = {"id": "fake_share_id"} + delete_size = params.get("delete_size", 3) + keys = params.get("keys", []) + scenario = self.scenario + scenario.clients = mock.MagicMock() + + scenario._delete_metadata(share, **params) + + scenario.clients.return_value.shares.delete_metadata.assert_has_calls([ + mock.call(share["id"], keys[i:i + delete_size]) + for i in range(0, len(keys), delete_size) + ])