diff --git a/doc/samples/tasks/glance/create-image-and-boot-instances.json b/doc/samples/tasks/glance/create-image-and-boot-instances.json index d2074d15b1..f82fa31b78 100644 --- a/doc/samples/tasks/glance/create-image-and-boot-instances.json +++ b/doc/samples/tasks/glance/create-image-and-boot-instances.json @@ -5,7 +5,9 @@ "image_location": "http://download.cirros-cloud.net/0.3.1/cirros-0.3.1-x86_64-disk.img", "container_format": "bare", "disk_format": "qcow2", - "flavor_id": 42, + "flavor": { + "name": "m1.nano" + }, "number_instances": 2 }, "runner": { diff --git a/doc/samples/tasks/glance/create-image-and-boot-instances.yaml b/doc/samples/tasks/glance/create-image-and-boot-instances.yaml index d1589cf9cd..bc36ef91ca 100644 --- a/doc/samples/tasks/glance/create-image-and-boot-instances.yaml +++ b/doc/samples/tasks/glance/create-image-and-boot-instances.yaml @@ -5,7 +5,8 @@ image_location: "http://download.cirros-cloud.net/0.3.1/cirros-0.3.1-x86_64-disk.img" container_format: "bare" disk_format: "qcow2" - flavor_id: 42 + flavor: + name: "m1.nano" number_instances: 2 runner: type: "constant" diff --git a/doc/samples/tasks/nova/boot-and-delete.json b/doc/samples/tasks/nova/boot-and-delete.json index c01b0a78e0..4316b07df9 100644 --- a/doc/samples/tasks/nova/boot-and-delete.json +++ b/doc/samples/tasks/nova/boot-and-delete.json @@ -2,8 +2,12 @@ "NovaServers.boot_and_delete_server": [ { "args": { - "flavor_id": 1, - "image_id": "73257560-c59b-4275-a1ec-ab140e5b9979" + "flavor": { + "name": "m1.nano" + }, + "image": { + "name": "cirros-0.3.1-x86_64-uec" + } }, "runner": { "type": "constant", diff --git a/doc/samples/tasks/nova/boot-and-delete.yaml b/doc/samples/tasks/nova/boot-and-delete.yaml index f310c77a2e..c814737908 100644 --- a/doc/samples/tasks/nova/boot-and-delete.yaml +++ b/doc/samples/tasks/nova/boot-and-delete.yaml @@ -2,8 +2,10 @@ NovaServers.boot_and_delete_server: - args: - flavor_id: 1 - image_id: "73257560-c59b-4275-a1ec-ab140e5b9979" + flavor: + name: "m1.nano" + image: + name: "cirros-0.3.1-x86_64-uec" runner: type: "constant" times: 10 diff --git a/doc/samples/tasks/nova/boot-and-list.json b/doc/samples/tasks/nova/boot-and-list.json index c31df73687..290be220ca 100644 --- a/doc/samples/tasks/nova/boot-and-list.json +++ b/doc/samples/tasks/nova/boot-and-list.json @@ -2,8 +2,12 @@ "NovaServers.boot_and_list_server": [ { "args": { - "flavor_id": 1, - "image_id": "ccc8fe37-9289-437b-a88c-fc4678656c75", + "flavor": { + "name": "m1.nano" + }, + "image": { + "name": "cirros-0.3.1-x86_64-uec" + }, "detailed" : True }, "runner": { diff --git a/doc/samples/tasks/nova/boot-and-list.yaml b/doc/samples/tasks/nova/boot-and-list.yaml index 06ec44ec7f..a474a356ce 100644 --- a/doc/samples/tasks/nova/boot-and-list.yaml +++ b/doc/samples/tasks/nova/boot-and-list.yaml @@ -2,8 +2,10 @@ NovaServers.boot_and_list_server: - args: - flavor_id: 1 - image_id: "ccc8fe37-9289-437b-a88c-fc4678656c75" + flavor: + name: "m1.nano" + image: + name: "cirros-0.3.1-x86_64-uec" detailed: True runner: type: "constant" diff --git a/doc/samples/tasks/nova/boot-bounce-delete.json b/doc/samples/tasks/nova/boot-bounce-delete.json index ef57f56539..7ebdde4518 100644 --- a/doc/samples/tasks/nova/boot-bounce-delete.json +++ b/doc/samples/tasks/nova/boot-bounce-delete.json @@ -1,27 +1,31 @@ -{ - "NovaServers.boot_and_bounce_server": [ - { - "args": { - "flavor_id": 1, - "image_id": "3fa4482f-677a-4488-adaf-c48befac5e5a", - "actions": [ - {"hard_reboot": 1}, - {"soft_reboot": 1}, - {"stop_start": 1}, - {"rescue_unrescue": 1} - ] - }, - "runner": { - "type": "constant", - "times": 10, - "concurrency": 2 - }, - "context": { - "users": { - "tenants": 3, - "users_per_tenant": 2 - } - } - } - ] -} +{ + "NovaServers.boot_and_bounce_server": [ + { + "args": { + "flavor": { + "name": "m1.nano" + }, + "image": { + "name": "cirros-0.3.1-x86_64-uec" + }, + "actions": [ + {"hard_reboot": 1}, + {"soft_reboot": 1}, + {"stop_start": 1}, + {"rescue_unrescue": 1} + ] + }, + "runner": { + "type": "constant", + "times": 10, + "concurrency": 2 + }, + "context": { + "users": { + "tenants": 3, + "users_per_tenant": 2 + } + } + } + ] +} diff --git a/doc/samples/tasks/nova/boot-bounce-delete.yaml b/doc/samples/tasks/nova/boot-bounce-delete.yaml index c2771dfd4a..e94ec5b6c8 100644 --- a/doc/samples/tasks/nova/boot-bounce-delete.yaml +++ b/doc/samples/tasks/nova/boot-bounce-delete.yaml @@ -1,23 +1,25 @@ ---- - NovaServers.boot_and_bounce_server: - - - args: - flavor_id: 1 - image_id: "3fa4482f-677a-4488-adaf-c48befac5e5a" - actions: - - - hard_reboot: 1 - - - soft_reboot: 1 - - - stop_start: 1 - - - rescue_unrescue: 1 - runner: - type: "constant" - times: 10 - concurrency: 2 - context: - users: - tenants: 3 - users_per_tenant: 2 +--- + NovaServers.boot_and_bounce_server: + - + args: + flavor: + name: "m1.nano" + image: + name: "cirros-0.3.1-x86_64-uec" + actions: + - + hard_reboot: 1 + - + soft_reboot: 1 + - + stop_start: 1 + - + rescue_unrescue: 1 + runner: + type: "constant" + times: 10 + concurrency: 2 + context: + users: + tenants: 3 + users_per_tenant: 2 diff --git a/doc/samples/tasks/nova/boot-from-volume-and-delete.json b/doc/samples/tasks/nova/boot-from-volume-and-delete.json index 35d15e8060..140cc3decc 100644 --- a/doc/samples/tasks/nova/boot-from-volume-and-delete.json +++ b/doc/samples/tasks/nova/boot-from-volume-and-delete.json @@ -2,8 +2,12 @@ "NovaServers.boot_server_from_volume_and_delete": [ { "args": { - "flavor_id": 1, - "image_id": "73257560-c59b-4275-a1ec-ab140e5b9979", + "flavor": { + "name": "m1.nano" + }, + "image": { + "name": "cirros-0.3.1-x86_64-uec" + }, "volume_size": 10 }, "runner": { diff --git a/doc/samples/tasks/nova/boot-from-volume-and-delete.yaml b/doc/samples/tasks/nova/boot-from-volume-and-delete.yaml index a312aab452..78b0c6a12f 100644 --- a/doc/samples/tasks/nova/boot-from-volume-and-delete.yaml +++ b/doc/samples/tasks/nova/boot-from-volume-and-delete.yaml @@ -2,8 +2,10 @@ NovaServers.boot_server_from_volume_and_delete: - args: - flavor_id: 1 - image_id: "73257560-c59b-4275-a1ec-ab140e5b9979" + flavor: + name: "m1.nano" + image: + name: "cirros-0.3.1-x86_64-uec" volume_size: 10 runner: type: "constant" diff --git a/doc/samples/tasks/nova/boot-from-volume.json b/doc/samples/tasks/nova/boot-from-volume.json index 65e6c69ac5..b6a500b183 100644 --- a/doc/samples/tasks/nova/boot-from-volume.json +++ b/doc/samples/tasks/nova/boot-from-volume.json @@ -2,8 +2,12 @@ "NovaServers.boot_server_from_volume": [ { "args": { - "flavor_id": 1, - "image_id": "73257560-c59b-4275-a1ec-ab140e5b9979", + "flavor": { + "name": "m1.nano" + }, + "image": { + "name": "cirros-0.3.1-x86_64-uec" + }, "volume_size": 10 }, "runner": { diff --git a/doc/samples/tasks/nova/boot-from-volume.yaml b/doc/samples/tasks/nova/boot-from-volume.yaml index 17cac2bd08..7295b2e4c1 100644 --- a/doc/samples/tasks/nova/boot-from-volume.yaml +++ b/doc/samples/tasks/nova/boot-from-volume.yaml @@ -2,8 +2,10 @@ NovaServers.boot_server_from_volume: - args: - flavor_id: 1 - image_id: "73257560-c59b-4275-a1ec-ab140e5b9979" + flavor: + name: "m1.nano" + image: + name: "cirros-0.3.1-x86_64-uec" volume_size: 10 runner: type: "constant" diff --git a/doc/samples/tasks/nova/boot-snapshot-boot-delete.json b/doc/samples/tasks/nova/boot-snapshot-boot-delete.json index 2d1661f451..0b2ed1ec99 100644 --- a/doc/samples/tasks/nova/boot-snapshot-boot-delete.json +++ b/doc/samples/tasks/nova/boot-snapshot-boot-delete.json @@ -2,8 +2,12 @@ "NovaServers.snapshot_server": [ { "args": { - "flavor_id": 1, - "image_id": "73257560-c59b-4275-a1ec-ab140e5b9979" + "flavor": { + "name": "m1.nano" + }, + "image": { + "name": "cirros-0.3.1-x86_64-uec" + } }, "runner": { "type": "constant", diff --git a/doc/samples/tasks/nova/boot-snapshot-boot-delete.yaml b/doc/samples/tasks/nova/boot-snapshot-boot-delete.yaml index 2332896b85..7365aa5509 100644 --- a/doc/samples/tasks/nova/boot-snapshot-boot-delete.yaml +++ b/doc/samples/tasks/nova/boot-snapshot-boot-delete.yaml @@ -2,8 +2,10 @@ NovaServers.snapshot_server: - args: - flavor_id: 1 - image_id: "73257560-c59b-4275-a1ec-ab140e5b9979" + flavor: + name: "m1.nano" + image: + name: "cirros-0.3.1-x86_64-uec" runner: type: "constant" times: 10 diff --git a/doc/samples/tasks/nova/boot.json b/doc/samples/tasks/nova/boot.json index a5e5dc738c..00b6059416 100644 --- a/doc/samples/tasks/nova/boot.json +++ b/doc/samples/tasks/nova/boot.json @@ -2,8 +2,12 @@ "NovaServers.boot_server": [ { "args": { - "flavor_id": 1, - "image_id": "73257560-c59b-4275-a1ec-ab140e5b9979" + "flavor": { + "name": "m1.nano" + }, + "image": { + "name": "cirros-0.3.1-x86_64-uec" + } }, "runner": { "type": "constant", diff --git a/doc/samples/tasks/nova/boot.yaml b/doc/samples/tasks/nova/boot.yaml index 1501052186..f37f97165a 100644 --- a/doc/samples/tasks/nova/boot.yaml +++ b/doc/samples/tasks/nova/boot.yaml @@ -2,8 +2,10 @@ NovaServers.boot_server: - args: - flavor_id: 1 - image_id: "73257560-c59b-4275-a1ec-ab140e5b9979" + flavor: + name: "m1.nano" + image: + name: "cirros-0.3.1-x86_64-uec" runner: type: "constant" times: 10 diff --git a/doc/samples/tasks/vm/boot-runcommand-delete.json b/doc/samples/tasks/vm/boot-runcommand-delete.json index 4cd085ba2e..e4ff505cf9 100644 --- a/doc/samples/tasks/vm/boot-runcommand-delete.json +++ b/doc/samples/tasks/vm/boot-runcommand-delete.json @@ -2,8 +2,12 @@ "VMTasks.boot_runcommand_delete": [ { "args": { - "flavor_id": "100", - "image_id": "8ec0b1bc-bb9c-4176-9d06-e23016091a51", + "flavor": { + "name": "m1.nano" + }, + "image": { + "name": "cirros-0.3.1-x86_64-uec" + }, "script": "doc/samples/support/instance_dd_test.sh", "interpreter": "/bin/sh", "username": "cirros" diff --git a/doc/samples/tasks/vm/boot-runcommand-delete.yaml b/doc/samples/tasks/vm/boot-runcommand-delete.yaml index 29b2a00fcd..531210b7ca 100644 --- a/doc/samples/tasks/vm/boot-runcommand-delete.yaml +++ b/doc/samples/tasks/vm/boot-runcommand-delete.yaml @@ -2,8 +2,10 @@ VMTasks.boot_runcommand_delete: - args: - flavor_id: "1" - image_id: "231bdb41-f959-4496-a272-3ef9f6d9f790" + flavor: + name: "m1.nano" + image: + name: "cirros-0.3.1-x86_64-uec" script: "doc/samples/support/instance_dd_test.sh" interpreter: "/bin/sh" username: "ubuntu" diff --git a/rally/benchmark/engine.py b/rally/benchmark/engine.py index 3f316af21e..469c635031 100644 --- a/rally/benchmark/engine.py +++ b/rally/benchmark/engine.py @@ -113,8 +113,8 @@ class BenchmarkEngine(object): reason=six.text_type(e) ) - def _validate_config_sematic_helper(self, admin, user, name, pos, - task, kwargs): + def _validate_config_semantic_helper(self, admin, user, name, pos, + task, kwargs): args = {} if not kwargs else kwargs.get("args", {}) try: base_scenario.Scenario.validate(name, args, admin=admin, @@ -139,9 +139,9 @@ class BenchmarkEngine(object): for name, values in config.iteritems(): for pos, kwargs in enumerate(values): - self._validate_config_sematic_helper(admin, user, name, - pos, self.task, - kwargs) + self._validate_config_semantic_helper(admin, user, name, + pos, self.task, + kwargs) @rutils.log_task_wrapper(LOG.info, _("Task validation.")) def validate(self): diff --git a/rally/benchmark/runners/base.py b/rally/benchmark/runners/base.py index fba52bcdf9..922c881ce7 100644 --- a/rally/benchmark/runners/base.py +++ b/rally/benchmark/runners/base.py @@ -207,6 +207,7 @@ class ScenarioRunner(object): "config": scenario_context } + args = cls.preprocess(method_name, context_obj, args) results = base_ctx.ContextManager.run(context_obj, self._run_scenario, cls, method_name, context_obj, args) diff --git a/rally/benchmark/scenarios/base.py b/rally/benchmark/scenarios/base.py index 5185d4d262..efc6be0da0 100644 --- a/rally/benchmark/scenarios/base.py +++ b/rally/benchmark/scenarios/base.py @@ -20,6 +20,7 @@ import time from rally import consts from rally import exceptions +from rally import osclients from rally import utils @@ -138,6 +139,22 @@ class Scenario(object): method = getattr(cls, method_name) return getattr(method, attr_name, default) + @classmethod + def preprocess(cls, method_name, context, args): + """Run preprocessor on scenario arguments.""" + preprocessors = Scenario.meta(cls, method_name=method_name, + attr_name="preprocessors", default={}) + clients = osclients.Clients(context["admin"]["endpoint"]) + + for src, preprocessor in preprocessors.items(): + resource_config = args.get(src) + if resource_config: + args[src] = preprocessor.transform( + clients=clients, + resource_config=resource_config) + + return args + def context(self): """Returns the context of the current benchmark scenario.""" return self._context diff --git a/rally/benchmark/scenarios/glance/images.py b/rally/benchmark/scenarios/glance/images.py index 6cc9fb9c42..ce74157ee8 100644 --- a/rally/benchmark/scenarios/glance/images.py +++ b/rally/benchmark/scenarios/glance/images.py @@ -16,6 +16,7 @@ from rally.benchmark.scenarios import base from rally.benchmark.scenarios.glance import utils from rally.benchmark.scenarios.nova import utils as nova_utils +from rally.benchmark import types as types from rally.benchmark import validation @@ -57,11 +58,12 @@ class GlanceImages(utils.GlanceScenario, nova_utils.NovaScenario): **kwargs) self._delete_image(image) - @validation.add_validator(validation.flavor_exists("flavor_id")) + @types.set(flavor=types.FlavorResourceType) + @validation.add_validator(validation.flavor_exists("flavor")) @base.scenario(context={"cleanup": ["glance", "nova"]}) def create_image_and_boot_instances(self, container_format, image_location, disk_format, - flavor_id, number_instances, + flavor, number_instances, **kwargs): """Test adds image, boots instance from it and then deletes them.""" image_name = self._generate_random_name() @@ -73,4 +75,4 @@ class GlanceImages(utils.GlanceScenario, nova_utils.NovaScenario): image_id = image.id server_name = self._generate_random_name(prefix="rally_novaserver_") self._boot_servers(server_name, image_id, - flavor_id, number_instances, **kwargs) + flavor, number_instances, **kwargs) diff --git a/rally/benchmark/scenarios/nova/servers.py b/rally/benchmark/scenarios/nova/servers.py index 6bbe548e20..d8819c19c7 100644 --- a/rally/benchmark/scenarios/nova/servers.py +++ b/rally/benchmark/scenarios/nova/servers.py @@ -20,6 +20,7 @@ from rally.benchmark.scenarios import base from rally.benchmark.scenarios.cinder import utils as cinder_utils from rally.benchmark.scenarios.nova import utils from rally.benchmark.scenarios import utils as scenario_utils +from rally.benchmark import types as types from rally.benchmark import validation as valid from rally import exceptions as rally_exceptions from rally.openstack.common.gettextutils import _ # noqa @@ -38,9 +39,11 @@ class NovaServers(utils.NovaScenario, def __init__(self, *args, **kwargs): super(NovaServers, self).__init__(*args, **kwargs) - @valid.add_validator(valid.image_valid_on_flavor("flavor_id", "image_id")) + @types.set(image=types.ImageResourceType, + flavor=types.FlavorResourceType) + @valid.add_validator(valid.image_valid_on_flavor("flavor", "image")) @base.scenario(context={"cleanup": ["nova"]}) - def boot_and_list_server(self, image_id, flavor_id, + def boot_and_list_server(self, image, flavor, detailed=True, **kwargs): """Tests booting an image and then listing servers. @@ -54,37 +57,43 @@ class NovaServers(utils.NovaScenario, the number of servers owned by users. """ self._boot_server( - self._generate_random_name(), image_id, flavor_id, **kwargs) + self._generate_random_name(), image, flavor, **kwargs) self._list_servers(detailed) - @valid.add_validator(valid.image_valid_on_flavor("flavor_id", "image_id")) + @types.set(image=types.ImageResourceType, + flavor=types.FlavorResourceType) + @valid.add_validator(valid.image_valid_on_flavor("flavor", "image")) @base.scenario(context={"cleanup": ["nova"]}) - def boot_and_delete_server(self, image_id, flavor_id, + def boot_and_delete_server(self, image, flavor, min_sleep=0, max_sleep=0, **kwargs): """Tests booting and then deleting an image.""" server = self._boot_server( - self._generate_random_name(), image_id, flavor_id, **kwargs) + self._generate_random_name(), image, flavor, **kwargs) self.sleep_between(min_sleep, max_sleep) self._delete_server(server) - @valid.add_validator(valid.image_valid_on_flavor("flavor_id", "image_id")) + @types.set(image=types.ImageResourceType, + flavor=types.FlavorResourceType) + @valid.add_validator(valid.image_valid_on_flavor("flavor", "image")) @base.scenario(context={"cleanup": ["nova", "cinder"]}) - def boot_server_from_volume_and_delete(self, image_id, flavor_id, + def boot_server_from_volume_and_delete(self, image, flavor, volume_size, min_sleep=0, max_sleep=0, **kwargs): """Tests booting from volume and then deleting an image and volume.""" - volume = self._create_volume(volume_size, imageRef=image_id) + volume = self._create_volume(volume_size, imageRef=image) block_device_mapping = {'vda': '%s:::1' % volume.id} server = self._boot_server(self._generate_random_name(), - image_id, flavor_id, + image, flavor, block_device_mapping=block_device_mapping, **kwargs) self.sleep_between(min_sleep, max_sleep) self._delete_server(server) - @valid.add_validator(valid.image_valid_on_flavor("flavor_id", "image_id")) + @types.set(image=types.ImageResourceType, + flavor=types.FlavorResourceType) + @valid.add_validator(valid.image_valid_on_flavor("flavor", "image")) @base.scenario(context={"cleanup": ["nova"]}) - def boot_and_bounce_server(self, image_id, flavor_id, **kwargs): + def boot_and_bounce_server(self, image, flavor, **kwargs): """Tests booting a server then performing stop/start or hard/soft reboot a number of times. """ @@ -97,28 +106,32 @@ class NovaServers(utils.NovaScenario, "Invalid server actions configuration \'%(actions)s\' due to: " "%(error)s" % {'actions': str(actions), 'error': str(error)}) server = self._boot_server(self._generate_random_name(), - image_id, flavor_id, **kwargs) + image, flavor, **kwargs) for action in action_builder.build_actions(actions, server): action() self._delete_server(server) - @valid.add_validator(valid.image_valid_on_flavor("flavor_id", "image_id")) + @types.set(image=types.ImageResourceType, + flavor=types.FlavorResourceType) + @valid.add_validator(valid.image_valid_on_flavor("flavor", "image")) @base.scenario(context={"cleanup": ["nova", "glance"]}) - def snapshot_server(self, image_id, flavor_id, **kwargs): + def snapshot_server(self, image, flavor, **kwargs): """Tests Nova instance snapshotting.""" server_name = self._generate_random_name() - server = self._boot_server(server_name, image_id, flavor_id, **kwargs) + server = self._boot_server(server_name, image, flavor, **kwargs) image = self._create_image(server) self._delete_server(server) - server = self._boot_server(server_name, image.id, flavor_id, **kwargs) + server = self._boot_server(server_name, image.id, flavor, **kwargs) self._delete_server(server) self._delete_image(image) - @valid.add_validator(valid.image_valid_on_flavor("flavor_id", "image_id")) + @types.set(image=types.ImageResourceType, + flavor=types.FlavorResourceType) + @valid.add_validator(valid.image_valid_on_flavor("flavor", "image")) @base.scenario(context={"cleanup": ["nova"]}) - def boot_server(self, image_id, flavor_id, **kwargs): + def boot_server(self, image, flavor, **kwargs): """Test VM boot - assumed clean-up is done elsewhere.""" if 'nics' not in kwargs: nets = self.clients("nova").networks.list() @@ -126,11 +139,13 @@ class NovaServers(utils.NovaScenario, random_nic = random.choice(nets) kwargs['nics'] = [{'net-id': random_nic.id}] self._boot_server( - self._generate_random_name(), image_id, flavor_id, **kwargs) + self._generate_random_name(), image, flavor, **kwargs) - @valid.add_validator(valid.image_valid_on_flavor("flavor_id", "image_id")) + @types.set(image=types.ImageResourceType, + flavor=types.FlavorResourceType) + @valid.add_validator(valid.image_valid_on_flavor("flavor", "image")) @base.scenario(context={"cleanup": ["nova", "cinder"]}) - def boot_server_from_volume(self, image_id, flavor_id, + def boot_server_from_volume(self, image, flavor, volume_size, **kwargs): """Test VM boot from volume - assumed clean-up is done elsewhere.""" if 'nics' not in kwargs: @@ -138,10 +153,10 @@ class NovaServers(utils.NovaScenario, if nets: random_nic = random.choice(nets) kwargs['nics'] = [{'net-id': random_nic.id}] - volume = self._create_volume(volume_size, imageRef=image_id) + volume = self._create_volume(volume_size, imageRef=image) block_device_mapping = {'vda': '%s:::1' % volume.id} self._boot_server(self._generate_random_name(), - image_id, flavor_id, + image, flavor, block_device_mapping=block_device_mapping, **kwargs) diff --git a/rally/benchmark/scenarios/vm/vmtasks.py b/rally/benchmark/scenarios/vm/vmtasks.py index 31b7ea3c77..dd6648b6bb 100644 --- a/rally/benchmark/scenarios/vm/vmtasks.py +++ b/rally/benchmark/scenarios/vm/vmtasks.py @@ -18,6 +18,7 @@ import json from rally.benchmark.scenarios import base from rally.benchmark.scenarios.nova import utils as nova_utils from rally.benchmark.scenarios.vm import utils as vm_utils +from rally.benchmark import types as types from rally.benchmark import validation as valid from rally.openstack.common.gettextutils import _ # noqa from rally.openstack.common import log as logging @@ -31,13 +32,15 @@ class VMTasks(nova_utils.NovaScenario, vm_utils.VMScenario): def __init__(self, *args, **kwargs): super(VMTasks, self).__init__(*args, **kwargs) - @valid.add_validator(valid.image_valid_on_flavor("flavor_id", "image_id")) + @types.set(image=types.ImageResourceType, + flavor=types.FlavorResourceType) + @valid.add_validator(valid.image_valid_on_flavor("flavor", "image")) @valid.add_validator(valid.file_exists("script")) @valid.add_validator(valid.number("port", minval=1, maxval=65535, nullable=True, integer_only=True)) @base.scenario(context={"cleanup": ["nova"], "keypair": {}, "allow_ssh": {}}) - def boot_runcommand_delete(self, image_id, flavor_id, + def boot_runcommand_delete(self, image, flavor, script, interpreter, network='private', username='ubuntu', ip_version=4, port=22, **kwargs): @@ -59,7 +62,7 @@ class VMTasks(nova_utils.NovaScenario, vm_utils.VMScenario): """ server = self._boot_server( self._generate_random_name("rally_novaserver_"), - image_id, flavor_id, key_name='rally_ssh_key', **kwargs) + image, flavor, key_name='rally_ssh_key', **kwargs) code, out, err = self.run_command(server, username, network, port, ip_version, interpreter, script) diff --git a/rally/benchmark/types.py b/rally/benchmark/types.py new file mode 100644 index 0000000000..8f6aaf781a --- /dev/null +++ b/rally/benchmark/types.py @@ -0,0 +1,130 @@ +# Copyright (C) 2014 Yahoo! Inc. All Rights Reserved. +# 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 abc +import operator +import re + +from rally import exceptions + + +def set(**kwargs): + """Decorator to define resource transformation(s) on scenario parameters. + + The `kwargs` passed as arguments to the decorator are used to + map a key in the scenario config to the subclass of ResourceType + used to perform a transformation on the value of that key. + """ + def wrapper(func): + func.preprocessors = getattr(func, 'preprocessors', {}) + func.preprocessors.update(kwargs) + return func + return wrapper + + +class ResourceType(object): + + @classmethod + @abc.abstractmethod + def transform(cls, clients, resource_config): + """Transform the resource. + + :param clients: openstack admin client handles + :param resource_config: scenario config of resource + + :returns: transformed value of resource + """ + pass + + +def _id_from_name(resource_config, resources, typename): + """Return the id of the resource whose name matches the pattern. + + When resource_config contains `name`, an exact match is used. + When resource_config contains `regex`, a pattern match is used. + + An `InvalidScenarioArgument` is thrown if the pattern does + not match unambiguously. + + :param resource_config: resource to be transformed + :param resources: iterable containing all resources + :param typename: name which describes the type of resource + + :returns: resource id uniquely mapped to `name` or `regex` + """ + if resource_config.get('name'): + patternstr = "^{0}$".format(resource_config.get('name')) + elif resource_config.get('regex'): + patternstr = resource_config.get('regex') + else: + raise exceptions.InvalidScenarioArgument( + "{typename} 'id', 'name', or 'regex' not found " + "in '{resource_config}' ".format(typename=typename.title(), + resource_config=resource_config)) + + pattern = re.compile(patternstr) + matching = filter(lambda resource: re.search(pattern, resource.name), + resources) + if not matching: + raise exceptions.InvalidScenarioArgument( + "{typename} with pattern '{pattern}' not found".format( + typename=typename.title(), pattern=pattern.pattern)) + elif len(matching) > 1: + raise exceptions.InvalidScenarioArgument( + "{typename} with name '{pattern}' is ambiguous, " + "possible matches by id: {ids}".format( + typename=typename.title(), pattern=pattern.pattern, + ids=", ".join(map(operator.attrgetter("id"), matching)))) + return matching[0].id + + +class FlavorResourceType(ResourceType): + + @classmethod + def transform(cls, clients, resource_config): + """Transform the resource config to id. + + :param clients: openstack admin client handles + :param resource_config: scenario config with `id`, `name` or `regex` + + :returns: id matching resource + """ + resource_id = resource_config.get('id') + if not resource_id: + novaclient = clients.nova() + resource_id = _id_from_name(resource_config=resource_config, + resources=novaclient. + flavors.list(), typename='flavor') + return resource_id + + +class ImageResourceType(ResourceType): + + @classmethod + def transform(cls, clients, resource_config): + """Transform the resource config to id. + + :param clients: openstack admin client handles + :param resource_config: scenario config with `id`, `name` or `regex` + + :returns: id matching resource + """ + resource_id = resource_config.get('id') + if not resource_id: + glanceclient = clients.glance() + resource_id = _id_from_name(resource_config=resource_config, + resources=glanceclient. + images.list(), typename='image') + return resource_id diff --git a/rally/benchmark/validation.py b/rally/benchmark/validation.py index 219d5037d5..0c9b1a1fe6 100644 --- a/rally/benchmark/validation.py +++ b/rally/benchmark/validation.py @@ -19,6 +19,7 @@ import os from glanceclient import exc as glance_exc from novaclient import exceptions as nova_exc +from rally.benchmark import types as types from rally import consts from rally.openstack.common.gettextutils import _ from rally.verification.verifiers.tempest import tempest @@ -144,10 +145,12 @@ def image_exists(param_name): to get image id value. """ def image_exists_validator(**kwargs): - image_id = kwargs.get(param_name) - glanceclient = kwargs["clients"].glance() + clients = kwargs.get('clients') + image_id = types.ImageResourceType.transform(clients=clients, + resource_config= + kwargs.get(param_name)) try: - glanceclient.images.get(image=image_id) + clients.glance().images.get(image=image_id) return ValidationResult() except glance_exc.HTTPNotFound: message = _("Image with id '%s' not found") % image_id @@ -162,10 +165,12 @@ def flavor_exists(param_name): to get flavor id value. """ def flavor_exists_validator(**kwargs): - flavor_id = kwargs.get(param_name) - novaclient = kwargs["clients"].nova() + clients = kwargs.get('clients') + flavor_id = types.FlavorResourceType.transform(clients=clients, + resource_config= + kwargs.get(param_name)) try: - novaclient.flavors.get(flavor=flavor_id) + clients.nova().flavors.get(flavor=flavor_id) return ValidationResult() except nova_exc.NotFound: message = _("Flavor with id '%s' not found") % flavor_id @@ -183,20 +188,22 @@ def image_valid_on_flavor(flavor_name, image_name): """ def image_valid_on_flavor_validator(**kwargs): - flavor_id = kwargs.get(flavor_name) - novaclient = kwargs["clients"].nova() + clients = kwargs.get('clients') + flavor_id = types.FlavorResourceType.transform(clients=clients, + resource_config= + kwargs.get(flavor_name)) try: - flavor = novaclient.flavors.get(flavor=flavor_id) + flavor = clients.nova().flavors.get(flavor=flavor_id) except nova_exc.NotFound: message = _("Flavor with id '%s' not found") % flavor_id return ValidationResult(False, message) - image_id = kwargs.get(image_name) - glanceclient = kwargs["clients"].glance() - + image_id = types.ImageResourceType.transform(clients=clients, + resource_config= + kwargs.get(image_name)) try: - image = glanceclient.images.get(image=image_id) + image = clients.glance().images.get(image=image_id) except glance_exc.HTTPNotFound: message = _("Image with id '%s' not found") % image_id return ValidationResult(False, message) @@ -216,7 +223,6 @@ def image_valid_on_flavor(flavor_name, image_name): message = _("The disk size for flavor '%s' is too small " "for requested image '%s'") % (flavor_id, image_id) return ValidationResult(False, message) - return ValidationResult() return image_valid_on_flavor_validator diff --git a/tests/benchmark/runners/test_base.py b/tests/benchmark/runners/test_base.py index 552d60f836..c7166c3233 100644 --- a/tests/benchmark/runners/test_base.py +++ b/tests/benchmark/runners/test_base.py @@ -204,14 +204,16 @@ class ScenarioRunnerTestCase(test.TestCase): config, serial.SerialScenarioRunner.CONFIG_SCHEMA) + @mock.patch("rally.benchmark.runners.base.osclients") @mock.patch("rally.benchmark.runners.base.base_ctx.ContextManager") - def test_run(self, mock_ctx_manager): + def test_run(self, mock_ctx_manager, mock_osclients): runner = constant.ConstantScenarioRunner(mock.MagicMock(), self.fake_endpoints, mock.MagicMock()) mock_ctx_manager.run.return_value = base.ScenarioRunnerResult([]) scenario_name = "NovaServers.boot_server_from_volume_and_delete" - result = runner.run(scenario_name, {"some_ctx": 2}, [1, 2, 3]) + config_kwargs = {"image": {"id": 1}, "flavor": {"id": 1}} + result = runner.run(scenario_name, {"some_ctx": 2}, config_kwargs) self.assertEqual(result, mock_ctx_manager.run.return_value) @@ -228,7 +230,7 @@ class ScenarioRunnerTestCase(test.TestCase): } expected = [context_obj, runner._run_scenario, cls, method_name, - context_obj, [1, 2, 3]] + context_obj, config_kwargs] mock_ctx_manager.run.assert_called_once_with(*expected) @mock.patch("rally.benchmark.runners.base.base_ctx.ContextManager") diff --git a/tests/benchmark/test_engine.py b/tests/benchmark/test_engine.py index 2470c9a82e..532d7cb353 100644 --- a/tests/benchmark/test_engine.py +++ b/tests/benchmark/test_engine.py @@ -158,8 +158,8 @@ class BenchmarkEngineTestCase(test.TestCase): def test__validate_config_semantic_helper(self, mock_validate): task = mock.MagicMock() eng = engine.BenchmarkEngine(mock.MagicMock(), mock.MagicMock()) - eng._validate_config_sematic_helper("admin", "user", "name", "pos", - task, {"args": "args"}) + eng._validate_config_semantic_helper("admin", "user", "name", "pos", + task, {"args": "args"}) mock_validate.assert_called_once_with( "name", "args", admin="admin", users=["user"], task=task) @@ -170,15 +170,15 @@ class BenchmarkEngineTestCase(test.TestCase): eng = engine.BenchmarkEngine(mock.MagicMock(), mock.MagicMock()) self.assertRaises(exceptions.InvalidBenchmarkConfig, - eng._validate_config_sematic_helper, "a", "u", "n", + eng._validate_config_semantic_helper, "a", "u", "n", "p", mock.MagicMock(), {}) @mock.patch("rally.benchmark.engine.osclients.Clients") @mock.patch("rally.benchmark.engine.users_ctx") @mock.patch("rally.benchmark.engine.BenchmarkEngine" - "._validate_config_sematic_helper") - def test__validate_config_sematic(self, mock_helper, mock_userctx, - mock_osclients): + "._validate_config_semantic_helper") + def test__validate_config_semantic(self, mock_helper, mock_userctx, + mock_osclients): mock_userctx.UserGenerator = fakes.FakeUserContext mock_osclients.return_value = mock.MagicMock() config = { diff --git a/tests/benchmark/test_types.py b/tests/benchmark/test_types.py new file mode 100644 index 0000000000..1188e65572 --- /dev/null +++ b/tests/benchmark/test_types.py @@ -0,0 +1,122 @@ +# Copyright (C) 2014 Yahoo! Inc. All Rights Reserved. +# 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 rally.benchmark import types +from rally import exceptions + +from tests import fakes +from tests import test + + +class FlavorResourceTypeTestCase(test.TestCase): + + def setUp(self): + super(FlavorResourceTypeTestCase, self).setUp() + self.clients = fakes.FakeClients() + self.clients.nova().flavors._cache(fakes.FakeResource(name='m1.tiny', + id=1)) + self.clients.nova().flavors._cache(fakes.FakeResource(name='m1.nano', + id=42)) + + def test_transform_by_id(self): + resource_config = {"id": 42} + flavor_id = types.FlavorResourceType.transform(clients=self.clients, + resource_config= + resource_config) + self.assertEqual(flavor_id, 42) + + def test_transform_by_name(self): + resource_config = {"name": "m1.nano"} + flavor_id = types.FlavorResourceType.transform(clients=self.clients, + resource_config= + resource_config) + self.assertEqual(flavor_id, 42) + + def test_transform_by_name_to_dest(self): + resource_config = {"name": "m1.nano"} + flavor_id = types.FlavorResourceType.transform(clients=self.clients, + resource_config= + resource_config) + self.assertEqual(flavor_id, 42) + + def test_transform_by_name_no_match(self): + resource_config = {"name": "m1.medium"} + self.assertRaises(exceptions.InvalidScenarioArgument, + types.FlavorResourceType.transform, self.clients, + resource_config) + + def test_transform_by_regex(self): + resource_config = {"regex": "m(1|2)\.nano"} + flavor_id = types.FlavorResourceType.transform(clients=self.clients, + resource_config= + resource_config) + self.assertEqual(flavor_id, 42) + + def test_transform_by_regex_no_match(self): + resource_config = {"regex": "m(2|3)\.nano"} + self.assertRaises(exceptions.InvalidScenarioArgument, + types.FlavorResourceType.transform, self.clients, + resource_config) + + +class ImageResourceTypeTestCase(test.TestCase): + + def setUp(self): + super(ImageResourceTypeTestCase, self).setUp() + self.clients = fakes.FakeClients() + image1 = fakes.FakeResource(name='cirros-0.3.1-uec', id=100) + self.clients.glance().images._cache(image1) + image2 = fakes.FakeResource(name='cirros-0.3.1-uec-ramdisk', id=101) + self.clients.glance().images._cache(image2) + + def test_transform_by_id(self): + resource_config = {"id": 100} + image_id = types.ImageResourceType.transform(clients=self.clients, + resource_config= + resource_config) + self.assertEqual(image_id, 100) + + def test_transform_by_name(self): + resource_config = {"name": "cirros-0.3.1-uec"} + image_id = types.ImageResourceType.transform(clients=self.clients, + resource_config= + resource_config) + self.assertEqual(image_id, 100) + + def test_transform_by_name_to_dest(self): + resource_config = {"name": "cirros-0.3.1-uec"} + image_id = types.ImageResourceType.transform(clients=self.clients, + resource_config= + resource_config) + self.assertEqual(image_id, 100) + + def test_transform_by_name_no_match(self): + resource_config = {"name": "cirros-0.3.1-uec-boot"} + self.assertRaises(exceptions.InvalidScenarioArgument, + types.ImageResourceType.transform, self.clients, + resource_config) + + def test_transform_by_regex(self): + resource_config = {"regex": "-uec$"} + image_id = types.ImageResourceType.transform(clients=self.clients, + resource_config= + resource_config) + self.assertEqual(image_id, 100) + + def test_transform_by_regex_no_match(self): + resource_config = {"regex": "-boot$"} + self.assertRaises(exceptions.InvalidScenarioArgument, + types.ImageResourceType.transform, self.clients, + resource_config) diff --git a/tests/benchmark/test_validation.py b/tests/benchmark/test_validation.py index fc9f9ce30f..709226cd19 100644 --- a/tests/benchmark/test_validation.py +++ b/tests/benchmark/test_validation.py @@ -124,10 +124,11 @@ class ValidationUtilsTestCase(test.TestCase): fakegclient = fakes.FakeGlanceClient() fakegclient.images.get = mock.MagicMock() mock_osclients.glance.return_value = fakegclient - validator = validation.image_exists("image_id") + validator = validation.image_exists("image") test_img_id = "test_image_id" + resource = {"id": test_img_id} result = validator(clients=mock_osclients, - image_id=test_img_id) + image=resource) fakegclient.images.get.assert_called_once_with(image=test_img_id) self.assertTrue(result.is_valid) self.assertIsNone(result.msg) @@ -138,10 +139,11 @@ class ValidationUtilsTestCase(test.TestCase): fakegclient.images.get = mock.MagicMock() fakegclient.images.get.side_effect = glance_exc.HTTPNotFound mock_osclients.glance.return_value = fakegclient - validator = validation.image_exists("image_id") + validator = validation.image_exists("image") test_img_id = "test_image_id" + resource = {"id": test_img_id} result = validator(clients=mock_osclients, - image_id=test_img_id) + image=resource) fakegclient.images.get.assert_called_once_with(image=test_img_id) self.assertFalse(result.is_valid) self.assertIsNotNone(result.msg) @@ -151,10 +153,11 @@ class ValidationUtilsTestCase(test.TestCase): fakenclient = fakes.FakeNovaClient() fakenclient.flavors = mock.MagicMock() mock_osclients.nova.return_value = fakenclient - validator = validation.flavor_exists("flavor_id") + validator = validation.flavor_exists("flavor") test_flavor_id = 1 + resource = {"id": test_flavor_id} result = validator(clients=mock_osclients, - flavor_id=test_flavor_id) + flavor=resource) fakenclient.flavors.get.assert_called_once_with(flavor=test_flavor_id) self.assertTrue(result.is_valid) self.assertIsNone(result.msg) @@ -165,10 +168,11 @@ class ValidationUtilsTestCase(test.TestCase): fakenclient.flavors = mock.MagicMock() fakenclient.flavors.get.side_effect = nova_exc.NotFound(code=404) mock_osclients.nova.return_value = fakenclient - validator = validation.flavor_exists("flavor_id") + validator = validation.flavor_exists("flavor") test_flavor_id = 101 + resource = {"id": test_flavor_id} result = validator(clients=mock_osclients, - flavor_id=test_flavor_id) + flavor=resource) fakenclient.flavors.get.assert_called_once_with(flavor=test_flavor_id) self.assertFalse(result.is_valid) self.assertIsNotNone(result.msg) @@ -190,11 +194,11 @@ class ValidationUtilsTestCase(test.TestCase): fakenclient.flavors.get = mock.MagicMock(return_value=flavor) mock_osclients.nova.return_value = fakenclient - validator = validation.image_valid_on_flavor("flavor_id", "image_id") + validator = validation.image_valid_on_flavor("flavor", "image") result = validator(clients=mock_osclients, - flavor_id=flavor.id, - image_id=image.id) + flavor={"id": flavor.id}, + image={"id": image.id}) fakenclient.flavors.get.assert_called_once_with(flavor=flavor.id) fakegclient.images.get.assert_called_once_with(image=image.id) @@ -219,11 +223,11 @@ class ValidationUtilsTestCase(test.TestCase): fakenclient.flavors.get = mock.MagicMock(return_value=flavor) mock_osclients.nova.return_value = fakenclient - validator = validation.image_valid_on_flavor("flavor_id", "image_id") + validator = validation.image_valid_on_flavor("flavor", "image") result = validator(clients=mock_osclients, - flavor_id=flavor.id, - image_id=image.id) + flavor={"id": flavor.id}, + image={"id": image.id}) fakenclient.flavors.get.assert_called_once_with(flavor=flavor.id) fakegclient.images.get.assert_called_once_with(image=image.id) @@ -243,13 +247,13 @@ class ValidationUtilsTestCase(test.TestCase): fakenclient.flavors.get = mock.MagicMock(return_value=flavor) mock_osclients.nova.return_value = fakenclient - validator = validation.image_valid_on_flavor("flavor_id", "image_id") + validator = validation.image_valid_on_flavor("flavor", "image") test_img_id = "test_image_id" result = validator(clients=mock_osclients, - flavor_id=flavor.id, - image_id=test_img_id) + flavor={"id": flavor.id}, + image={"id": test_img_id}) fakenclient.flavors.get.assert_called_once_with(flavor=flavor.id) fakegclient.images.get.assert_called_once_with(image=test_img_id) @@ -266,14 +270,14 @@ class ValidationUtilsTestCase(test.TestCase): fakenclient.flavors.get.side_effect = nova_exc.NotFound(code=404) mock_osclients.nova.return_value = fakenclient - validator = validation.image_valid_on_flavor("flavor_id", "image_id") + validator = validation.image_valid_on_flavor("flavor", "image") test_img_id = "test_image_id" test_flavor_id = 101 result = validator(clients=mock_osclients, - flavor_id=test_flavor_id, - image_id=test_img_id) + flavor={"id": test_flavor_id}, + image={"id": test_img_id}) fakenclient.flavors.get.assert_called_once_with(flavor=test_flavor_id)