From 7a2580513f7da7e00a898cba77cd08f816b2a9a4 Mon Sep 17 00:00:00 2001 From: Daniel Mitterdorfer Date: Mon, 1 Apr 2019 19:27:42 +0200 Subject: [PATCH] Honor JAVA_HOME in provisioner We currently rely on `java` being available on the PATH when provisioning an Elasticsearch node but instead we should be consistent and always use the specified runtime JDK when provisoning a node. With this commit we determine the correct runtime JDK and set it as `JAVA_HOME` when provisioning a node. --- esrally/mechanic/java_resolver.py | 43 ++++++++++++++++ esrally/mechanic/launcher.py | 22 +------- esrally/mechanic/provisioner.py | 20 +++++--- tests/mechanic/provisioner_test.py | 82 +++++++++++++++++++++++------- 4 files changed, 121 insertions(+), 46 deletions(-) create mode 100644 esrally/mechanic/java_resolver.py diff --git a/esrally/mechanic/java_resolver.py b/esrally/mechanic/java_resolver.py new file mode 100644 index 000000000..de85c5e80 --- /dev/null +++ b/esrally/mechanic/java_resolver.py @@ -0,0 +1,43 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you 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 logging + +from esrally import exceptions +from esrally.utils import jvm + + +def java_home(car, cfg): + def determine_runtime_jdks(car): + override_runtime_jdk = cfg.opts("mechanic", "runtime.jdk") + if override_runtime_jdk: + return [override_runtime_jdk] + else: + runtime_jdks = car.mandatory_var("runtime.jdk") + try: + return [int(v) for v in runtime_jdks.split(",")] + except ValueError: + raise exceptions.SystemSetupError( + "Car config key \"runtime.jdk\" is invalid: \"{}\" (must be int)".format(runtime_jdks)) + + logger = logging.getLogger(__name__) + + runtime_jdk_versions = determine_runtime_jdks(car) + logger.info("Allowed JDK versions are %s.", runtime_jdk_versions) + major, java_home = jvm.resolve_path(runtime_jdk_versions) + logger.info("Detected JDK with major version [%s] in [%s].", major, java_home) + return major, java_home diff --git a/esrally/mechanic/launcher.py b/esrally/mechanic/launcher.py index 80a9cc97d..6b8ce6c6e 100644 --- a/esrally/mechanic/launcher.py +++ b/esrally/mechanic/launcher.py @@ -23,7 +23,7 @@ import shlex from esrally import config, time, exceptions, client -from esrally.mechanic import telemetry, cluster +from esrally.mechanic import telemetry, cluster, java_resolver from esrally.utils import process, jvm @@ -298,7 +298,6 @@ def __init__(self, cfg, metrics_store, races_root_dir, clock=time.Clock): self._clock = clock self.races_root_dir = races_root_dir self.keep_running = self.cfg.opts("mechanic", "keep.running") - self.override_runtime_jdk = self.cfg.opts("mechanic", "runtime.jdk") self.logger = logging.getLogger(__name__) def start(self, node_configurations): @@ -317,7 +316,7 @@ def _start_node(self, node_configuration, node_count_on_host): binary_path = node_configuration.binary_path data_paths = node_configuration.data_paths node_telemetry_dir = "%s/telemetry" % node_configuration.node_root_path - java_major_version, java_home = self._resolve_java_home(car) + java_major_version, java_home = java_resolver.java_home(car, self.cfg) self.logger.info("Starting node [%s] based on car [%s].", node_name, car) @@ -346,23 +345,6 @@ def _start_node(self, node_configuration, node_count_on_host): return node - def _resolve_java_home(self, car): - runtime_jdk_versions = self._determine_runtime_jdks(car) - self.logger.info("Allowed JDK versions are %s.", runtime_jdk_versions) - major, java_home = jvm.resolve_path(runtime_jdk_versions) - self.logger.info("Detected JDK with major version [%s] in [%s].", major, java_home) - return major, java_home - - def _determine_runtime_jdks(self, car): - if self.override_runtime_jdk: - return [self.override_runtime_jdk] - else: - runtime_jdks = car.mandatory_var("runtime.jdk") - try: - return [int(v) for v in runtime_jdks.split(",")] - except ValueError: - raise exceptions.SystemSetupError("Car config key \"runtime.jdk\" is invalid: \"{}\" (must be int)".format(runtime_jdks)) - def _prepare_env(self, car, node_name, java_home, t): env = {} env.update(os.environ) diff --git a/esrally/mechanic/provisioner.py b/esrally/mechanic/provisioner.py index 7def61e2b..1105774ba 100644 --- a/esrally/mechanic/provisioner.py +++ b/esrally/mechanic/provisioner.py @@ -23,7 +23,7 @@ import jinja2 from esrally import exceptions -from esrally.mechanic import team +from esrally.mechanic import team, java_resolver from esrally.utils import io, process, versions @@ -37,8 +37,10 @@ def local_provisioner(cfg, car, plugins, cluster_settings, all_node_ips, target_ node_name = "%s-%d" % (node_name_prefix, node_id) node_root_dir = "%s/%s" % (target_root, node_name) - es_installer = ElasticsearchInstaller(car, node_name, node_root_dir, all_node_ips, ip, http_port) - plugin_installers = [PluginInstaller(plugin) for plugin in plugins] + _, java_home = java_resolver.java_home(car, cfg) + + es_installer = ElasticsearchInstaller(car, java_home, node_name, node_root_dir, all_node_ips, ip, http_port) + plugin_installers = [PluginInstaller(plugin, java_home) for plugin in plugins] return BareProvisioner(cluster_settings, es_installer, plugin_installers, preserve, distribution_version=distribution_version) @@ -220,8 +222,9 @@ def _provisioner_variables(self): class ElasticsearchInstaller: - def __init__(self, car, node_name, node_root_dir, all_node_ips, ip, http_port, hook_handler_class=team.BootstrapHookHandler): + def __init__(self, car, java_home, node_name, node_root_dir, all_node_ips, ip, http_port, hook_handler_class=team.BootstrapHookHandler): self.car = car + self.java_home = java_home self.node_name = node_name self.node_root_dir = node_root_dir self.install_dir = "%s/install" % node_root_dir @@ -254,7 +257,7 @@ def delete_pre_bundled_configuration(self): shutil.rmtree(config_path) def invoke_install_hook(self, phase, variables): - self.hook_handler.invoke(phase.name, variables=variables) + self.hook_handler.invoke(phase.name, variables=variables, env={"JAVA_HOME": self.java_home}) def cleanup(self, preserve): cleanup(preserve, self.install_dir, self.data_paths) @@ -307,8 +310,9 @@ def _data_paths(self): class PluginInstaller: - def __init__(self, plugin, hook_handler_class=team.BootstrapHookHandler): + def __init__(self, plugin, java_home, hook_handler_class=team.BootstrapHookHandler): self.plugin = plugin + self.java_home = java_home self.hook_handler = hook_handler_class(self.plugin) if self.hook_handler.can_load(): self.hook_handler.load() @@ -323,7 +327,7 @@ def install(self, es_home_path, plugin_url=None): self.logger.info("Installing [%s] into [%s]", self.plugin_name, es_home_path) install_cmd = '%s install --batch "%s"' % (installer_binary_path, self.plugin_name) - return_code = process.run_subprocess_with_logging(install_cmd) + return_code = process.run_subprocess_with_logging(install_cmd, env={"JAVA_HOME": self.java_home}) # see: https://www.elastic.co/guide/en/elasticsearch/plugins/current/_other_command_line_parameters.html if return_code == 0: self.logger.info("Successfully installed [%s].", self.plugin_name) @@ -337,7 +341,7 @@ def install(self, es_home_path, plugin_url=None): (self.plugin_name, str(return_code))) def invoke_install_hook(self, phase, variables): - self.hook_handler.invoke(phase.name, variables=variables) + self.hook_handler.invoke(phase.name, variables=variables, env={"JAVA_HOME": self.java_home}) @property def variables(self): diff --git a/tests/mechanic/provisioner_test.py b/tests/mechanic/provisioner_test.py index 92921d99f..c209cf1ce 100644 --- a/tests/mechanic/provisioner_test.py +++ b/tests/mechanic/provisioner_test.py @@ -42,6 +42,7 @@ def null_apply_config(source_root_path, target_root_path, config_vars): root_path=None, config_paths=["~/.rally/benchmarks/teams/default/my-car"], variables={"heap": "4g"}), + java_home="/usr/local/javas/java8", node_name="rally-node-0", node_root_dir="~/.rally/benchmarks/races/unittest", all_node_ips=["10.17.22.22", "10.17.22.23"], @@ -91,8 +92,11 @@ def __init__(self, plugin): def can_load(self): return False - def invoke(self, phase, variables): - self.hook_calls[phase] = variables + def invoke(self, phase, variables, **kwargs): + self.hook_calls[phase] = { + "variables": variables, + "kwargs": kwargs + } class MockRallyTeamXPackPlugin: """ @@ -144,6 +148,7 @@ def null_apply_config(source_root_path, target_root_path, config_vars): root_path=None, config_paths=["~/.rally/benchmarks/teams/default/my-car"], variables={"heap": "4g"}), + java_home="/usr/local/javas/java8", node_name="rally-node-0", node_root_dir="~/.rally/benchmarks/races/unittest", all_node_ips=["10.17.22.22", "10.17.22.23"], @@ -154,6 +159,7 @@ def null_apply_config(source_root_path, target_root_path, config_vars): es_installer=installer, plugin_installers=[ provisioner.PluginInstaller(BareProvisionerTests.MockRallyTeamXPackPlugin(), + java_home="/usr/local/javas/java8", hook_handler_class=BareProvisionerTests.NoopHookHandler) ], preserve=True, @@ -220,6 +226,7 @@ def null_apply_config(source_root_path, target_root_path, config_vars): root_path=None, config_paths=["~/.rally/benchmarks/teams/default/my-car"], variables={"heap": "4g"}), + java_home="/usr/local/javas/java8", node_name="rally-node-0", node_root_dir="~/.rally/benchmarks/races/unittest", all_node_ips=["10.17.22.22", "10.17.22.23"], @@ -230,6 +237,7 @@ def null_apply_config(source_root_path, target_root_path, config_vars): es_installer=installer, plugin_installers=[ provisioner.PluginInstaller(BareProvisionerTests.MockRallyTeamXPackPlugin(), + java_home="/usr/local/javas/java8", hook_handler_class=BareProvisionerTests.NoopHookHandler) ], preserve=True, @@ -281,8 +289,11 @@ def __init__(self, component): def can_load(self): return False - def invoke(self, phase, variables): - self.hook_calls[phase] = variables + def invoke(self, phase, variables, **kwargs): + self.hook_calls[phase] = { + "variables": variables, + "kwargs": kwargs, + } class ElasticsearchInstallerTests(TestCase): @@ -292,6 +303,7 @@ def test_cleanup_nothing_on_preserve(self, mock_path_exists, mock_rm): mock_path_exists.return_value = False installer = provisioner.ElasticsearchInstaller(car=team.Car("defaults", None, "/tmp"), + java_home="/usr/local/javas/java8", node_name="rally-node-0", all_node_ips={"127.0.0.1"}, ip="127.0.0.1", @@ -311,6 +323,7 @@ def test_cleanup(self, mock_path_exists, mock_rm): root_path=None, config_paths="/tmp", variables={"data_paths": "/tmp/some/data-path-dir"}), + java_home="/usr/local/javas/java8", node_name="rally-node-0", all_node_ips={"127.0.0.1"}, ip="127.0.0.1", @@ -330,6 +343,7 @@ def test_prepare_default_data_paths(self, mock_rm, mock_ensure_dir, mock_decompr installer = provisioner.ElasticsearchInstaller(car=team.Car(names="defaults", root_path=None, config_paths="/tmp"), + java_home="/usr/local/javas/java8", node_name="rally-node-0", all_node_ips=["10.17.22.22", "10.17.22.23"], ip="10.17.22.23", @@ -366,6 +380,7 @@ def test_prepare_user_provided_data_path(self, mock_rm, mock_ensure_dir, mock_de root_path=None, config_paths="/tmp", variables={"data_paths": "/tmp/some/data-path-dir"}), + java_home="/usr/local/javas/java8", node_name="rally-node-0", all_node_ips=["10.17.22.22", "10.17.22.23"], ip="10.17.22.23", @@ -398,6 +413,7 @@ def test_invokes_hook(self): root_path="/tmp", config_paths="/tmp/templates", variables={"data_paths": "/tmp/some/data-path-dir"}), + java_home="/usr/local/javas/java8", node_name="rally-node-0", all_node_ips=["10.17.22.22", "10.17.22.23"], ip="10.17.22.23", @@ -408,7 +424,9 @@ def test_invokes_hook(self): self.assertEqual(0, len(installer.hook_handler.hook_calls)) installer.invoke_install_hook(team.BootstrapPhase.post_install, {"foo": "bar"}) self.assertEqual(1, len(installer.hook_handler.hook_calls)) - self.assertEqual({"foo": "bar"}, installer.hook_handler.hook_calls["post_install"]) + self.assertEqual({"foo": "bar"}, installer.hook_handler.hook_calls["post_install"]["variables"]) + self.assertEqual({"env": {"JAVA_HOME": "/usr/local/javas/java8"}}, + installer.hook_handler.hook_calls["post_install"]["kwargs"]) class PluginInstallerTests(TestCase): @@ -417,11 +435,15 @@ def test_install_plugin_successfully(self, installer_subprocess): installer_subprocess.return_value = 0 plugin = team.PluginDescriptor(name="unit-test-plugin", config="default", variables={"active": True}) - installer = provisioner.PluginInstaller(plugin, hook_handler_class=NoopHookHandler) + installer = provisioner.PluginInstaller(plugin, + java_home="/usr/local/javas/java8", + hook_handler_class=NoopHookHandler) installer.install(es_home_path="/opt/elasticsearch") - installer_subprocess.assert_called_with('/opt/elasticsearch/bin/elasticsearch-plugin install --batch "unit-test-plugin"') + installer_subprocess.assert_called_with( + '/opt/elasticsearch/bin/elasticsearch-plugin install --batch "unit-test-plugin"', + env={"JAVA_HOME": "/usr/local/javas/java8"}) @mock.patch("esrally.utils.process.run_subprocess_with_logging") def test_install_unknown_plugin(self, installer_subprocess): @@ -429,13 +451,17 @@ def test_install_unknown_plugin(self, installer_subprocess): installer_subprocess.return_value = 64 plugin = team.PluginDescriptor(name="unknown") - installer = provisioner.PluginInstaller(plugin, hook_handler_class=NoopHookHandler) + installer = provisioner.PluginInstaller(plugin, + java_home="/usr/local/javas/java8", + hook_handler_class=NoopHookHandler) with self.assertRaises(exceptions.SystemSetupError) as ctx: installer.install(es_home_path="/opt/elasticsearch") self.assertEqual("Unknown plugin [unknown]", ctx.exception.args[0]) - installer_subprocess.assert_called_with('/opt/elasticsearch/bin/elasticsearch-plugin install --batch "unknown"') + installer_subprocess.assert_called_with( + '/opt/elasticsearch/bin/elasticsearch-plugin install --batch "unknown"', + env={"JAVA_HOME": "/usr/local/javas/java8"}) @mock.patch("esrally.utils.process.run_subprocess_with_logging") def test_install_plugin_with_io_error(self, installer_subprocess): @@ -443,13 +469,17 @@ def test_install_plugin_with_io_error(self, installer_subprocess): installer_subprocess.return_value = 74 plugin = team.PluginDescriptor(name="simple") - installer = provisioner.PluginInstaller(plugin, hook_handler_class=NoopHookHandler) + installer = provisioner.PluginInstaller(plugin, + java_home="/usr/local/javas/java8", + hook_handler_class=NoopHookHandler) with self.assertRaises(exceptions.SupplyError) as ctx: installer.install(es_home_path="/opt/elasticsearch") self.assertEqual("I/O error while trying to install [simple]", ctx.exception.args[0]) - installer_subprocess.assert_called_with('/opt/elasticsearch/bin/elasticsearch-plugin install --batch "simple"') + installer_subprocess.assert_called_with( + '/opt/elasticsearch/bin/elasticsearch-plugin install --batch "simple"', + env={"JAVA_HOME": "/usr/local/javas/java8"}) @mock.patch("esrally.utils.process.run_subprocess_with_logging") def test_install_plugin_with_unknown_error(self, installer_subprocess): @@ -457,31 +487,47 @@ def test_install_plugin_with_unknown_error(self, installer_subprocess): installer_subprocess.return_value = 12987 plugin = team.PluginDescriptor(name="simple") - installer = provisioner.PluginInstaller(plugin, hook_handler_class=NoopHookHandler) + installer = provisioner.PluginInstaller(plugin, + java_home="/usr/local/javas/java8", + hook_handler_class=NoopHookHandler) with self.assertRaises(exceptions.RallyError) as ctx: installer.install(es_home_path="/opt/elasticsearch") self.assertEqual("Unknown error while trying to install [simple] (installer return code [12987]). Please check the logs.", ctx.exception.args[0]) - installer_subprocess.assert_called_with('/opt/elasticsearch/bin/elasticsearch-plugin install --batch "simple"') + installer_subprocess.assert_called_with( + '/opt/elasticsearch/bin/elasticsearch-plugin install --batch "simple"', + env={"JAVA_HOME": "/usr/local/javas/java8"}) def test_pass_plugin_properties(self): - plugin = team.PluginDescriptor(name="unit-test-plugin", config="default", config_paths=["/etc/plugin"], variables={"active": True}) - installer = provisioner.PluginInstaller(plugin, hook_handler_class=NoopHookHandler) + plugin = team.PluginDescriptor(name="unit-test-plugin", + config="default", + config_paths=["/etc/plugin"], + variables={"active": True}) + installer = provisioner.PluginInstaller(plugin, + java_home="/usr/local/javas/java8", + hook_handler_class=NoopHookHandler) self.assertEqual("unit-test-plugin", installer.plugin_name) self.assertEqual({"active": True}, installer.variables) self.assertEqual(["/etc/plugin"], installer.config_source_paths) def test_invokes_hook(self): - plugin = team.PluginDescriptor(name="unit-test-plugin", config="default", config_paths=["/etc/plugin"], variables={"active": True}) - installer = provisioner.PluginInstaller(plugin, hook_handler_class=NoopHookHandler) + plugin = team.PluginDescriptor(name="unit-test-plugin", + config="default", + config_paths=["/etc/plugin"], + variables={"active": True}) + installer = provisioner.PluginInstaller(plugin, + java_home="/usr/local/javas/java8", + hook_handler_class=NoopHookHandler) self.assertEqual(0, len(installer.hook_handler.hook_calls)) installer.invoke_install_hook(team.BootstrapPhase.post_install, {"foo": "bar"}) self.assertEqual(1, len(installer.hook_handler.hook_calls)) - self.assertEqual({"foo": "bar"}, installer.hook_handler.hook_calls["post_install"]) + self.assertEqual({"foo": "bar"}, installer.hook_handler.hook_calls["post_install"]["variables"]) + self.assertEqual({"env": {"JAVA_HOME": "/usr/local/javas/java8"}}, + installer.hook_handler.hook_calls["post_install"]["kwargs"]) class DockerProvisionerTests(TestCase):