Add scenario to modify Cinder volume metadata

This performs multiple modifications of Cinder volume metadata and
benchmarks metadata modifications and deletions.

Change-Id: Ie5b0fa14a6988d4c37dbfdedbadcc562e3250f44
This commit is contained in:
Chris St. Pierre 2015-04-17 08:52:39 -05:00
parent acebc9e2e7
commit c8def8766b
7 changed files with 195 additions and 1 deletions

View File

@ -628,6 +628,23 @@
failure_rate: failure_rate:
max: 0 max: 0
CinderVolumes.modify_volume_metadata:
-
args: {}
runner:
type: "constant"
times: 3
concurrency: 3
context:
volumes:
size: 1
users:
tenants: 1
users_per_tenant: 1
sla:
failure_rate:
max: 0
CinderVolumes.list_volumes: CinderVolumes.list_volumes:
- -
args: args:

View File

@ -20,7 +20,7 @@ from oslo_config import cfg
from rally.benchmark.scenarios import base from rally.benchmark.scenarios import base
from rally.benchmark import utils as bench_utils from rally.benchmark import utils as bench_utils
from rally import exceptions
CINDER_BENCHMARK_OPTS = [ CINDER_BENCHMARK_OPTS = [
cfg.FloatOpt("cinder_volume_create_prepoll_delay", cfg.FloatOpt("cinder_volume_create_prepoll_delay",
@ -65,6 +65,55 @@ class CinderScenario(base.Scenario):
return self.clients("cinder").volume_snapshots.list(detailed) return self.clients("cinder").volume_snapshots.list(detailed)
def _set_metadata(self, volume, sets=10, set_size=3):
"""Set volume metadata.
:param volume: The volume to set metadata on
:param sets: how many operations to perform
:param set_size: number of metadata keys to set in each operation
:returns: A list of keys that were set
"""
key = "cinder.set_%s_metadatas_%s_times" % (set_size, sets)
with base.AtomicAction(self, key):
keys = []
for i in range(sets):
metadata = {}
for j in range(set_size):
key = self._generate_random_name()
keys.append(key)
metadata[key] = self._generate_random_name()
self.clients("cinder").volumes.set_metadata(volume, metadata)
return keys
def _delete_metadata(self, volume, keys, deletes=10, delete_size=3):
"""Delete volume metadata keys.
Note that ``len(keys)`` must be greater than or equal to
``deletes * delete_size``.
:param volume: The volume to delete metadata from
:param deletes: how many operations to perform
:param delete_size: number of metadata keys to delete in each operation
:param keys: a list of keys to choose deletion candidates from
"""
if len(keys) < deletes * delete_size:
raise exceptions.InvalidArgumentsException(
"Not enough metadata keys to delete: "
"%(num_keys)s keys, but asked to delete %(num_deletes)s" %
{"num_keys": len(keys),
"num_deletes": deletes * delete_size})
# make a shallow copy of the list of keys so that, when we pop
# from it later, we don't modify the original list.
keys = list(keys)
random.shuffle(keys)
action_name = "cinder.delete_%s_metadatas_%s_times" % (delete_size,
deletes)
with base.AtomicAction(self, action_name):
for i in range(deletes):
to_del = keys[i * delete_size:(i + 1) * delete_size]
self.clients("cinder").volumes.delete_metadata(volume, to_del)
@base.atomic_action_timer("cinder.create_volume") @base.atomic_action_timer("cinder.create_volume")
def _create_volume(self, size, **kwargs): def _create_volume(self, size, **kwargs):
"""Create one volume. """Create one volume.

View File

@ -20,6 +20,7 @@ from rally.benchmark import types as types
from rally.benchmark import validation from rally.benchmark import validation
from rally.common import log as logging from rally.common import log as logging
from rally import consts from rally import consts
from rally import exceptions
from rally.plugins.openstack.scenarios.cinder import utils from rally.plugins.openstack.scenarios.cinder import utils
from rally.plugins.openstack.scenarios.glance import utils as glance_utils from rally.plugins.openstack.scenarios.glance import utils as glance_utils
from rally.plugins.openstack.scenarios.nova import utils as nova_utils from rally.plugins.openstack.scenarios.nova import utils as nova_utils
@ -135,6 +136,36 @@ class CinderVolumes(utils.CinderScenario,
self._create_volume(size, **kwargs) self._create_volume(size, **kwargs)
@validation.required_services(consts.Service.CINDER)
@validation.required_openstack(users=True)
@validation.required_contexts("volumes")
@base.scenario(context={"cleanup": ["cinder"]})
def modify_volume_metadata(self, sets=10, set_size=3,
deletes=5, delete_size=3):
"""Modify a volume's metadata.
This requires a volume to be created with the volumes
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 deletes: how many delete_metadata operations to perform
:param delete_size: number of metadata keys to delete in each
delete_metadata operation
"""
if sets * set_size < deletes * delete_size:
raise exceptions.InvalidArgumentsException(
"Not enough metadata keys will be created: "
"Setting %(num_keys)s keys, but deleting %(num_deletes)s" %
{"num_keys": sets * set_size,
"num_deletes": deletes * delete_size})
volume = random.choice(self.context["tenant"]["volumes"])
keys = self._set_metadata(volume["id"], sets, set_size)
self._delete_metadata(volume["id"], keys, deletes, delete_size)
@validation.required_services(consts.Service.CINDER) @validation.required_services(consts.Service.CINDER)
@validation.required_openstack(users=True) @validation.required_openstack(users=True)
@base.scenario(context={"cleanup": ["cinder"]}) @base.scenario(context={"cleanup": ["cinder"]})

View File

@ -0,0 +1,21 @@
{
"CinderVolumes.modify_volume_metadata": [
{
"args": {},
"runner": {
"type": "constant",
"times": 10,
"concurrency": 2
},
"context": {
"volumes": {
"size": 1
},
"users": {
"tenants": 2,
"users_per_tenant": 2
}
}
}
]
}

View File

@ -0,0 +1,14 @@
---
CinderVolumes.modify_volume_metadata:
-
args: {}
runner:
type: "constant"
times: 10
concurrency: 2
context:
volumes:
size: 1
users:
tenants: 2
users_per_tenant: 2

View File

@ -17,7 +17,9 @@ import mock
from oslo_config import cfg from oslo_config import cfg
from oslotest import mockpatch from oslotest import mockpatch
from rally import exceptions
from rally.plugins.openstack.scenarios.cinder import utils from rally.plugins.openstack.scenarios.cinder import utils
from tests.unit import fakes
from tests.unit import test from tests.unit import test
BM_UTILS = "rally.benchmark.utils" BM_UTILS = "rally.benchmark.utils"
@ -59,6 +61,51 @@ class CinderScenarioTestCase(test.TestCase):
self._test_atomic_action_timer(self.scenario.atomic_actions(), self._test_atomic_action_timer(self.scenario.atomic_actions(),
"cinder.list_snapshots") "cinder.list_snapshots")
@mock.patch(CINDER_UTILS + ".CinderScenario.clients")
def test__set_metadata(self, mock_clients):
volume = fakes.FakeVolume()
self.scenario._set_metadata(volume, sets=2, set_size=4)
calls = mock_clients("cinder").volumes.set_metadata.call_args_list
self.assertEqual(len(calls), 2)
for call in calls:
call_volume, metadata = call[0]
self.assertEqual(call_volume, volume)
self.assertEqual(len(metadata), 4)
self._test_atomic_action_timer(self.scenario.atomic_actions(),
"cinder.set_4_metadatas_2_times")
@mock.patch(CINDER_UTILS + ".CinderScenario.clients")
def test__delete_metadata(self, mock_clients):
volume = fakes.FakeVolume()
keys = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l"]
self.scenario._delete_metadata(volume, keys, deletes=3, delete_size=4)
calls = mock_clients("cinder").volumes.delete_metadata.call_args_list
self.assertEqual(len(calls), 3)
all_deleted = []
for call in calls:
call_volume, del_keys = call[0]
self.assertEqual(call_volume, volume)
self.assertEqual(len(del_keys), 4)
for key in del_keys:
self.assertIn(key, keys)
self.assertNotIn(key, all_deleted)
all_deleted.append(key)
self._test_atomic_action_timer(self.scenario.atomic_actions(),
"cinder.delete_4_metadatas_3_times")
@mock.patch(CINDER_UTILS + ".CinderScenario.clients")
def test__delete_metadata_not_enough_keys(self, mock_clients):
volume = fakes.FakeVolume()
keys = ["a", "b", "c", "d", "e"]
self.assertRaises(exceptions.InvalidArgumentsException,
self.scenario._delete_metadata,
volume, keys, deletes=2, delete_size=3)
@mock.patch(CINDER_UTILS + ".CinderScenario.clients") @mock.patch(CINDER_UTILS + ".CinderScenario.clients")
def test__create_volume(self, mock_clients): def test__create_volume(self, mock_clients):
CONF = cfg.CONF CONF = cfg.CONF

View File

@ -66,6 +66,21 @@ class CinderServersTestCase(test.TestCase):
scenario.create_volume(1, fakearg="f") scenario.create_volume(1, fakearg="f")
scenario._create_volume.assert_called_once_with(1, fakearg="f") scenario._create_volume.assert_called_once_with(1, fakearg="f")
def test_create_volume_and_modify_metadata(self):
scenario = volumes.CinderVolumes(
context={"user": {"tenant_id": "fake"},
"tenant": {"id": "fake", "name": "fake",
"volumes": [{"id": "uuid"}]}})
scenario._set_metadata = mock.Mock()
scenario._delete_metadata = mock.Mock()
scenario.modify_volume_metadata(sets=5, set_size=4,
deletes=3, delete_size=2)
scenario._set_metadata.assert_called_once_with("uuid", 5, 4)
scenario._delete_metadata.assert_called_once_with(
"uuid",
scenario._set_metadata.return_value, 3, 2)
def test_create_and_extend_volume(self): def test_create_and_extend_volume(self):
fake_volume = mock.MagicMock() fake_volume = mock.MagicMock()