diff --git a/rally-jobs/nova.yaml b/rally-jobs/nova.yaml index fab4b9c5..c9a66369 100644 --- a/rally-jobs/nova.yaml +++ b/rally-jobs/nova.yaml @@ -599,8 +599,20 @@ name: {{image_name}} floating_network: "public" use_floatingip: true - script: "/home/jenkins/.rally/extra/instance_dd_test.sh" - interpreter: "/bin/sh" + command: + script_inline: | + time_seconds(){ (time -p $1 ) 2>&1 |awk '/real/{print $2}'; } + file=/tmp/test.img + c=100 #100M + write_seq=$(time_seconds "dd if=/dev/zero of=$file bs=1M count=$c") + read_seq=$(time_seconds "dd if=$file of=/dev/null bs=1M count=$c") + [ -f $file ] && rm $file + + echo "{ + \"write_seq\": $write_seq, + \"read_seq\": $read_seq + }" + interpreter: "/bin/sh" username: "cirros" runner: type: "constant" @@ -623,8 +635,9 @@ volume_args: size: 2 use_floatingip: true - script: "/home/jenkins/.rally/extra/instance_dd_test.sh" - interpreter: "/bin/sh" + command: + script_file: "/home/jenkins/.rally/extra/instance_dd_test.sh" + interpreter: "/bin/sh" username: "cirros" runner: type: "constant" @@ -645,8 +658,9 @@ image: name: {{image_name}} use_floatingip: false - script: "/home/jenkins/.rally/extra/instance_dd_test.sh" - interpreter: "/bin/sh" + command: + script_file: "/home/jenkins/.rally/extra/instance_dd_test.sh" + interpreter: "/bin/sh" username: "cirros" runner: type: "constant" diff --git a/rally-jobs/rally-mos.yaml b/rally-jobs/rally-mos.yaml index 9877e4eb..75261175 100644 --- a/rally-jobs/rally-mos.yaml +++ b/rally-jobs/rally-mos.yaml @@ -295,8 +295,9 @@ name: "TestVM|cirros.*uec" floating_network: "net04_ext" use_floatingip: true - script: "/home/rally/.rally/extra/instance_dd_test.sh" - interpreter: "/bin/sh" + command: + script_file: "/home/rally/.rally/extra/instance_dd_test.sh" + interpreter: "/bin/sh" username: "cirros" runner: type: "constant" diff --git a/rally-jobs/rally-neutron.yaml b/rally-jobs/rally-neutron.yaml index c3a1a72c..9d4c4055 100644 --- a/rally-jobs/rally-neutron.yaml +++ b/rally-jobs/rally-neutron.yaml @@ -459,8 +459,9 @@ name: "m1.tiny" image: name: {{image_name}} - script: "/home/jenkins/.rally/extra/instance_dd_test.sh" - interpreter: "/bin/sh" + command: + script_file: "/home/jenkins/.rally/extra/instance_dd_test.sh" + interpreter: "/bin/sh" username: "cirros" runner: type: "constant" diff --git a/rally/plugins/openstack/scenarios/vm/utils.py b/rally/plugins/openstack/scenarios/vm/utils.py index 3a9a6654..11faaf37 100644 --- a/rally/plugins/openstack/scenarios/vm/utils.py +++ b/rally/plugins/openstack/scenarios/vm/utils.py @@ -47,7 +47,8 @@ class VMScenario(base.Scenario): :param ssh: A SSHClient instance. :param command: Dictionary specifying command to execute. - See `validation.valid_command' docstring for details. + See `rally info find VMTasks.boot_runcommand_delete' parameter + `command' docstring for explanation. :returns: tuple (exit_status, stdout, stderr) """ @@ -150,7 +151,8 @@ class VMScenario(base.Scenario): :param username: str. ssh username for server :param password: Password for SSH authentication :param command: Dictionary specifying command to execute. - See `valiation.valid_command' docstring for explanation. + See `rally info find VMTasks.boot_runcommand_delete' parameter + `command' docstring for explanation. :param pkey: key for SSH authentication :returns: tuple (exit_status, stdout, stderr) diff --git a/rally/plugins/openstack/scenarios/vm/vmtasks.py b/rally/plugins/openstack/scenarios/vm/vmtasks.py index d8f03115..66463cd3 100644 --- a/rally/plugins/openstack/scenarios/vm/vmtasks.py +++ b/rally/plugins/openstack/scenarios/vm/vmtasks.py @@ -15,6 +15,7 @@ import json +from rally.common import utils from rally import consts from rally import exceptions from rally.plugins.openstack.scenarios.cinder import utils as cinder_utils @@ -35,7 +36,10 @@ class VMTasks(nova_utils.NovaScenario, vm_utils.VMScenario, @types.set(image=types.ImageResourceType, flavor=types.FlavorResourceType) @validation.image_valid_on_flavor("flavor", "image") - @validation.file_exists("script") + @utils.log_deprecated_args("Use `command' argument instead", "0.0.5", + ("script", "interpreter"), once=True) + @validation.file_exists("script", required=False) + @validation.valid_command("command", required=False) @validation.number("port", minval=1, maxval=65535, nullable=True, integer_only=True) @validation.external_network_exists("floating_network") @@ -44,8 +48,11 @@ class VMTasks(nova_utils.NovaScenario, vm_utils.VMScenario, @base.scenario(context={"cleanup": ["nova", "cinder"], "keypair": {}, "allow_ssh": {}}) def boot_runcommand_delete(self, image, flavor, - script, interpreter, username, + username, password=None, + script=None, + interpreter=None, + command=None, volume_args=None, floating_network=None, port=22, @@ -58,11 +65,52 @@ class VMTasks(nova_utils.NovaScenario, vm_utils.VMScenario, :param image: glance image name to use for the vm :param flavor: VM flavor name - :param script: script to run on server, must output JSON mapping - metric names to values (see the sample script below) - :param interpreter: server's interpreter to run the script :param username: ssh username on server, str :param password: Password on SSH authentication + :param script: DEPRECATED. Use `command' instead. Script to run on + server, must output JSON mapping metric names to values (see the + sample script below) + :param interpreter: DEPRECATED. Use `command' instead. server's + interpreter to run the script + :param command: Command-specifying dictionary that either specifies + remote command path via `remote_path', an inline script via + `script_inline' or a local script file path using `script_file'. + The `script_file' is checked to be accessible by the `file_exists' + validator. + + The `script_inline' and `script_file' both require an `interpreter' + value to specify the interpreter script should be run with. + + Note that any of `interpreter' and `remote_path' can be an array + prefixed with environment variables and suffixed with args for + the `interpreter' command. + + Examples:: + + # Run a `local_script.pl' file sending it to a remote + # Perl interpreter + command = { + "script_file": "local_script.pl", + "interpreter": "/usr/bin/perl" + } + + # Run an inline script sending it to a remote interpreter + command = { + "script_inline": "echo 'Hello, World!'", + "interpreter": "/bin/sh" + } + + # Run a remote command + command = { + "remote_path": "/bin/false" + } + + # Run an inline script sending it to a remote interpreter + command = { + "script_inline": "echo \"Hello, ${NAME:-World}\"", + "interpreter": ["NAME=Earth", "/bin/sh"] + } + :param volume_args: volume args for booting server from volume :param floating_network: external network name, for floating ip :param port: ssh port for SSH connection @@ -74,6 +122,9 @@ class VMTasks(nova_utils.NovaScenario, vm_utils.VMScenario, errors: str, raw data from the script's stderr stream """ + if command is None and script and interpreter: + command = {"script_file": script, "interpreter": interpreter} + if volume_args: volume = self._create_volume(volume_args["size"], imageRef=None) kwargs["block_device_mapping"] = {"vdrally": "%s:::1" % volume.id} @@ -85,21 +136,20 @@ class VMTasks(nova_utils.NovaScenario, vm_utils.VMScenario, **kwargs) try: code, out, err = self._run_command( - fip["ip"], port, username, password, - command={"script_file": script, "interpreter": interpreter}) + fip["ip"], port, username, password, command=command) if code: raise exceptions.ScriptError( - "Error running script %(script)s. " + "Error running command %(command)s. " "Error %(code)s: %(error)s" % { - "script": script, "code": code, "error": err}) + "command": command, "code": code, "error": err}) try: data = json.loads(out) except ValueError as e: raise exceptions.ScriptError( - "Script %(script)s has not output valid JSON: %(error)s. " - "Output: %(output)s" % { - "script": script, "error": str(e), "output": out}) + "Command %(command)s has not output valid JSON: %(error)s." + " Output: %(output)s" % { + "command": command, "error": str(e), "output": out}) finally: self._delete_server_with_fip(server, fip, force_delete=force_delete) diff --git a/tests/unit/plugins/openstack/scenarios/vm/test_vmtasks.py b/tests/unit/plugins/openstack/scenarios/vm/test_vmtasks.py index f920da24..5c253363 100644 --- a/tests/unit/plugins/openstack/scenarios/vm/test_vmtasks.py +++ b/tests/unit/plugins/openstack/scenarios/vm/test_vmtasks.py @@ -15,6 +15,8 @@ import mock +from rally.common import log +from rally.common import utils from rally import exceptions from rally.plugins.openstack.scenarios.vm import vmtasks from tests.unit import test @@ -36,9 +38,41 @@ class VMTasksTestCase(test.TestCase): return_value=(0, "\"foo_out\"", "foo_err")) def test_boot_runcommand_delete(self): + with log.LogCatcher(utils.LOG) as catcher: + self.scenario.boot_runcommand_delete( + "foo_image", "foo_flavor", + script="foo_script", interpreter="foo_interpreter", + username="foo_username", + password="foo_password", + use_floating_ip="use_fip", + floating_network="ext_network", + force_delete="foo_force", + volume_args={"size": 16}, + foo_arg="foo_value") + + catcher.assertInLogs( + "Use `command' argument instead (args `script', `interpreter' " + "deprecated in Rally v0.0.5)") + + self.scenario._create_volume.assert_called_once_with( + 16, imageRef=None) + self.scenario._boot_server_with_fip.assert_called_once_with( + "foo_image", "foo_flavor", use_floating_ip="use_fip", + floating_network="ext_network", key_name="keypair_name", + block_device_mapping={"vdrally": "foo_volume:::1"}, + foo_arg="foo_value") + + self.scenario._run_command.assert_called_once_with( + "foo_ip", 22, "foo_username", "foo_password", + command={"script_file": "foo_script", + "interpreter": "foo_interpreter"}) + self.scenario._delete_server_with_fip.assert_called_once_with( + "foo_server", self.ip, force_delete="foo_force") + + def test_boot_runcommand_delete_command(self): self.scenario.boot_runcommand_delete( "foo_image", "foo_flavor", - script="foo_script", interpreter="foo_interpreter", + command={"remote_path": "foo"}, username="foo_username", password="foo_password", use_floating_ip="use_fip", @@ -57,8 +91,7 @@ class VMTasksTestCase(test.TestCase): self.scenario._run_command.assert_called_once_with( "foo_ip", 22, "foo_username", "foo_password", - command={"script_file": "foo_script", - "interpreter": "foo_interpreter"}) + command={"remote_path": "foo"}) self.scenario._delete_server_with_fip.assert_called_once_with( "foo_server", self.ip, force_delete="foo_force")