diff --git a/rally/plugins/openstack/scenarios/vm/utils.py b/rally/plugins/openstack/scenarios/vm/utils.py index 3608c8e2..fb52e4aa 100644 --- a/rally/plugins/openstack/scenarios/vm/utils.py +++ b/rally/plugins/openstack/scenarios/vm/utils.py @@ -21,10 +21,10 @@ import six from rally.benchmark.scenarios import base from rally.benchmark import utils as bench_utils +from rally.benchmark import validation from rally.common.i18n import _ from rally.common import log as logging from rally.common import sshutils -from rally import exceptions from rally.plugins.openstack.wrappers import network as network_wrapper LOG = logging.getLogger(__name__) @@ -40,32 +40,33 @@ class VMScenario(base.Scenario): """ @base.atomic_action_timer("vm.run_command_over_ssh") - def _run_command_over_ssh(self, ssh, interpreter, script, - is_file=True): + def _run_command_over_ssh(self, ssh, command): """Run command inside an instance. This is a separate function so that only script execution is timed. :param ssh: A SSHClient instance. - :param interpreter: The interpreter that will be used to execute - the script. - :param script: Path to the script file or its content in a StringIO. - :param is_file: if True, script represent a path, - else, script contains an inline script. + :param command: Dictionary specifying command to execute. + See `validation.valid_command' docstring for details. + :returns: tuple (exit_status, stdout, stderr) """ - if not is_file: - stdin = script - elif isinstance(script, six.string_types): - stdin = open(script, "rb") - elif isinstance(script, six.moves.StringIO): - stdin = script - else: - raise exceptions.ScriptError( - "Either file path or StringIO expected, given %s" % - type(script).__name__) + validation.check_command_dict(command) - return ssh.execute(interpreter, stdin=stdin) + # NOTE(pboldin): Here we `get' the values and not check for the keys + # due to template-driven configuration generation that can leave keys + # defined but values empty. + if command.get("script_file") or command.get("script_inline"): + cmd = command["interpreter"] + if command.get("script_file"): + stdin = open(command["script_file"], "rb") + elif command.get("script_inline"): + stdin = six.moves.StringIO(command["script_inline"]) + elif command.get("remote_path"): + cmd = command["remote_path"] + stdin = None + + return ssh.execute(cmd, stdin=stdin) def _boot_server_with_fip(self, image, flavor, use_floating_ip=True, floating_network=None, @@ -135,30 +136,30 @@ class VMScenario(base.Scenario): timeout=120 ) - def _run_command(self, server_ip, port, username, password, interpreter, - script, pkey=None, is_file=True): + def _run_command(self, server_ip, port, username, password, command, + pkey=None): """Run command via SSH on server. - Create SSH connection for server, wait for server to become - available (there is a delay between server being set to ACTIVE - and sshd being available). Then call run_command_over_ssh to actually - execute the command. + Create SSH connection for server, wait for server to become available + (there is a delay between server being set to ACTIVE and sshd being + available). Then call run_command_over_ssh to actually execute the + command. + :param server_ip: server ip address :param port: ssh port for SSH connection :param username: str. ssh username for server :param password: Password for SSH authentication - :param interpreter: server's interpreter to execute the script - :param script: script to run on server + :param command: Dictionary specifying command to execute. + See `valiation.valid_command' docstring for explanation. :param pkey: key for SSH authentication - :param is_file: if True, script represent a path, - else, script contains an inline script. + + :returns: tuple (exit_status, stdout, stderr) """ pkey = pkey if pkey else self.context["user"]["keypair"]["private"] ssh = sshutils.SSH(username, server_ip, port=port, pkey=pkey, password=password) self._wait_for_ssh(ssh) - return self._run_command_over_ssh(ssh, interpreter, - script, is_file) + return self._run_command_over_ssh(ssh, command) @staticmethod def _ping_ip_address(host): diff --git a/rally/plugins/openstack/scenarios/vm/vmtasks.py b/rally/plugins/openstack/scenarios/vm/vmtasks.py index ff1dcfc2..e3b2b07c 100644 --- a/rally/plugins/openstack/scenarios/vm/vmtasks.py +++ b/rally/plugins/openstack/scenarios/vm/vmtasks.py @@ -84,8 +84,9 @@ class VMTasks(nova_utils.NovaScenario, vm_utils.VMScenario, key_name=self.context["user"]["keypair"]["name"], **kwargs) try: - code, out, err = self._run_command(fip["ip"], port, username, - password, interpreter, script) + code, out, err = self._run_command( + fip["ip"], port, username, password, + command={"script_file": script, "interpreter": interpreter}) if code: raise exceptions.ScriptError( "Error running script %(script)s. " diff --git a/tests/unit/plugins/openstack/scenarios/vm/test_utils.py b/tests/unit/plugins/openstack/scenarios/vm/test_utils.py index 6ac6cddb..31233a33 100644 --- a/tests/unit/plugins/openstack/scenarios/vm/test_utils.py +++ b/tests/unit/plugins/openstack/scenarios/vm/test_utils.py @@ -19,9 +19,7 @@ import subprocess import mock import netaddr from oslotest import mockpatch -import six -from rally import exceptions from rally.plugins.openstack.scenarios.vm import utils from tests.unit import test @@ -38,26 +36,55 @@ class VMScenarioTestCase(test.TestCase): @mock.patch("%s.open" % VMTASKS_UTILS, side_effect=mock.mock_open(), create=True) - def test__run_command_over_ssh(self, mock_open): + def test__run_command_over_ssh_script_file(self, mock_open): mock_ssh = mock.MagicMock() vm_scenario = utils.VMScenario() - vm_scenario._run_command_over_ssh(mock_ssh, "interpreter", "script") - mock_ssh.execute.assert_called_once_with("interpreter", - stdin=mock_open.side_effect()) + vm_scenario._run_command_over_ssh( + mock_ssh, + { + "script_file": "foobar", + "interpreter": ["interpreter", "interpreter_arg"], + } + ) + mock_ssh.execute.assert_called_once_with( + ["interpreter", "interpreter_arg"], + stdin=mock_open.side_effect()) + mock_open.assert_called_once_with("foobar", "rb") - def test__run_command_over_ssh_stringio(self): + @mock.patch("%s.six.moves.StringIO" % VMTASKS_UTILS) + def test__run_command_over_ssh_script_inline(self, mock_stringio): mock_ssh = mock.MagicMock() vm_scenario = utils.VMScenario() - script = six.moves.StringIO("script") - vm_scenario._run_command_over_ssh(mock_ssh, "interpreter", script) - mock_ssh.execute.assert_called_once_with("interpreter", - stdin=script) + vm_scenario._run_command_over_ssh( + mock_ssh, + { + "script_inline": "foobar", + "interpreter": ["interpreter", "interpreter_arg"], + } + ) + mock_ssh.execute.assert_called_once_with( + ["interpreter", "interpreter_arg"], + stdin=mock_stringio.return_value) + mock_stringio.assert_called_once_with("foobar") + + def test__run_command_over_ssh_remote_path(self): + mock_ssh = mock.MagicMock() + vm_scenario = utils.VMScenario() + vm_scenario._run_command_over_ssh( + mock_ssh, + { + "remote_path": ["foo", "bar"], + } + ) + mock_ssh.execute.assert_called_once_with( + ["foo", "bar"], + stdin=None) def test__run_command_over_ssh_fails(self): vm_scenario = utils.VMScenario() - self.assertRaises(exceptions.ScriptError, + self.assertRaises(ValueError, vm_scenario._run_command_over_ssh, - None, "interpreter", 10) + None, command={}) def test__wait_for_ssh(self): ssh = mock.MagicMock() @@ -85,35 +112,17 @@ class VMScenarioTestCase(test.TestCase): vm_scenario = utils.VMScenario() vm_scenario.context = {"user": {"keypair": {"private": "ssh"}}} - vm_scenario._run_command("1.2.3.4", 22, "username", - "password", "int", "/path/to/foo/script.sh", - is_file=True) + vm_scenario._run_command("1.2.3.4", 22, "username", "password", + command={"script_file": "foo", + "interpreter": "bar"}) mock_ssh_class.assert_called_once_with("username", "1.2.3.4", port=22, pkey="ssh", password="password") mock_ssh_instance.wait.assert_called_once_with() mock_run_command_over_ssh.assert_called_once_with( - mock_ssh_instance, "int", "/path/to/foo/script.sh", True) - - @mock.patch(VMTASKS_UTILS + ".sshutils.SSH") - def test__run_command_inline_script(self, mock_ssh): - mock_ssh_instance = mock.MagicMock() - mock_ssh.return_value = mock_ssh_instance - mock_ssh_instance.execute.return_value = "foobar" - vm_scenario = utils.VMScenario() - vm_scenario._wait_for_ssh = mock.Mock() - vm_scenario.context = {"user": {"keypair": {"private": "foo_pkey"}}} - result = vm_scenario._run_command("foo_ip", "foo_port", "foo_username", - "foo_password", "foo_interpreter", - "foo_script", is_file=False) - mock_ssh.assert_called_once_with("foo_username", "foo_ip", - port="foo_port", pkey="foo_pkey", - password="foo_password") - vm_scenario._wait_for_ssh.assert_called_once_with(mock_ssh_instance) - mock_ssh_instance.execute.assert_called_once_with("foo_interpreter", - stdin="foo_script") - self.assertEqual(result, "foobar") + mock_ssh_instance, + {"script_file": "foo", "interpreter": "bar"}) @mock.patch(VMTASKS_UTILS + ".sys") @mock.patch("subprocess.Popen") diff --git a/tests/unit/plugins/openstack/scenarios/vm/test_vmtasks.py b/tests/unit/plugins/openstack/scenarios/vm/test_vmtasks.py index cd87fdbc..f920da24 100644 --- a/tests/unit/plugins/openstack/scenarios/vm/test_vmtasks.py +++ b/tests/unit/plugins/openstack/scenarios/vm/test_vmtasks.py @@ -37,8 +37,9 @@ class VMTasksTestCase(test.TestCase): def test_boot_runcommand_delete(self): self.scenario.boot_runcommand_delete( - "foo_image", "foo_flavor", "foo_script", - "foo_interpreter", "foo_username", + "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", @@ -56,7 +57,8 @@ class VMTasksTestCase(test.TestCase): self.scenario._run_command.assert_called_once_with( "foo_ip", 22, "foo_username", "foo_password", - "foo_interpreter", "foo_script") + 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")