/* * Licensed to Elasticsearch under one or more contributor * license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright * ownership. Elasticsearch 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. */ package org.elasticsearch.bootstrap; import org.apache.lucene.util.Constants; import org.apache.lucene.util.LuceneTestCase; import org.elasticsearch.Version; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.Environment; import org.elasticsearch.plugins.PluginTestUtil; import org.elasticsearch.plugins.Platforms; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.attribute.PosixFileAttributeView; import java.nio.file.attribute.PosixFilePermission; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasSize; /** * Create a simple "daemon controller", put it in the right place and check that it runs. * * Extends LuceneTestCase rather than ESTestCase as ESTestCase installs a system call filter, and * that prevents the Spawner class from doing its job. Also needs to run in a separate JVM to other * tests that extend ESTestCase for the same reason. */ public class SpawnerNoBootstrapTests extends LuceneTestCase { private static final String CONTROLLER_SOURCE = "#!/bin/bash\n" + "\n" + "echo I am alive\n" + "\n" + "read SOMETHING\n"; /** * Simplest case: a plugin with no controller daemon. */ public void testNoControllerSpawn() throws IOException, InterruptedException { Path esHome = createTempDir().resolve("esHome"); Settings.Builder settingsBuilder = Settings.builder(); settingsBuilder.put(Environment.PATH_HOME_SETTING.getKey(), esHome.toString()); Settings settings = settingsBuilder.build(); Environment environment = new Environment(settings); // This plugin will NOT have a controller daemon Path plugin = environment.pluginsFile().resolve("a_plugin"); Files.createDirectories(plugin); PluginTestUtil.writeProperties( plugin, "description", "a_plugin", "version", Version.CURRENT.toString(), "elasticsearch.version", Version.CURRENT.toString(), "name", "a_plugin", "java.version", "1.8", "classname", "APlugin", "has.native.controller", "false"); try (Spawner spawner = new Spawner()) { spawner.spawnNativePluginControllers(environment); assertThat(spawner.getProcesses(), hasSize(0)); } } /** * Two plugins - one with a controller daemon and one without. */ public void testControllerSpawn() throws IOException, InterruptedException { /* * On Windows you can not directly run a batch file - you have to run cmd.exe with the batch * file as an argument and that's out of the remit of the controller daemon process spawner. */ assumeFalse("This test does not work on Windows", Constants.WINDOWS); Path esHome = createTempDir().resolve("esHome"); Settings.Builder settingsBuilder = Settings.builder(); settingsBuilder.put(Environment.PATH_HOME_SETTING.getKey(), esHome.toString()); Settings settings = settingsBuilder.build(); Environment environment = new Environment(settings); // this plugin will have a controller daemon Path plugin = environment.pluginsFile().resolve("test_plugin"); Files.createDirectories(plugin); PluginTestUtil.writeProperties( plugin, "description", "test_plugin", "version", Version.CURRENT.toString(), "elasticsearch.version", Version.CURRENT.toString(), "name", "test_plugin", "java.version", "1.8", "classname", "TestPlugin", "has.native.controller", "true"); Path controllerProgram = Platforms.nativeControllerPath(plugin); createControllerProgram(controllerProgram); // this plugin will not have a controller daemon Path otherPlugin = environment.pluginsFile().resolve("other_plugin"); Files.createDirectories(otherPlugin); PluginTestUtil.writeProperties( otherPlugin, "description", "other_plugin", "version", Version.CURRENT.toString(), "elasticsearch.version", Version.CURRENT.toString(), "name", "other_plugin", "java.version", "1.8", "classname", "OtherPlugin", "has.native.controller", "false"); Spawner spawner = new Spawner(); spawner.spawnNativePluginControllers(environment); List<Process> processes = spawner.getProcesses(); /* * As there should only be a reference in the list for the plugin that had the controller * daemon, we expect one here. */ assertThat(processes, hasSize(1)); Process process = processes.get(0); final InputStreamReader in = new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8); try (BufferedReader stdoutReader = new BufferedReader(in)) { String line = stdoutReader.readLine(); assertEquals("I am alive", line); spawner.close(); /* * Fail if the process does not die within one second; usually it will be even quicker * but it depends on OS scheduling. */ assertTrue(process.waitFor(1, TimeUnit.SECONDS)); } } public void testControllerSpawnWithIncorrectDescriptor() throws IOException { // this plugin will have a controller daemon Path esHome = createTempDir().resolve("esHome"); Settings.Builder settingsBuilder = Settings.builder(); settingsBuilder.put(Environment.PATH_HOME_SETTING.getKey(), esHome.toString()); Settings settings = settingsBuilder.build(); Environment environment = new Environment(settings); Path plugin = environment.pluginsFile().resolve("test_plugin"); Files.createDirectories(plugin); PluginTestUtil.writeProperties( plugin, "description", "test_plugin", "version", Version.CURRENT.toString(), "elasticsearch.version", Version.CURRENT.toString(), "name", "test_plugin", "java.version", "1.8", "classname", "TestPlugin", "has.native.controller", "false"); Path controllerProgram = Platforms.nativeControllerPath(plugin); createControllerProgram(controllerProgram); Spawner spawner = new Spawner(); IllegalArgumentException e = expectThrows( IllegalArgumentException.class, () -> spawner.spawnNativePluginControllers(environment)); assertThat( e.getMessage(), equalTo("plugin [test_plugin] does not have permission to fork native controller")); } private void createControllerProgram(final Path outputFile) throws IOException { final Path outputDir = outputFile.getParent(); Files.createDirectories(outputDir); Files.write(outputFile, CONTROLLER_SOURCE.getBytes(StandardCharsets.UTF_8)); final PosixFileAttributeView view = Files.getFileAttributeView(outputFile, PosixFileAttributeView.class); if (view != null) { final Set<PosixFilePermission> perms = new HashSet<>(); perms.add(PosixFilePermission.OWNER_READ); perms.add(PosixFilePermission.OWNER_WRITE); perms.add(PosixFilePermission.OWNER_EXECUTE); perms.add(PosixFilePermission.GROUP_READ); perms.add(PosixFilePermission.GROUP_EXECUTE); perms.add(PosixFilePermission.OTHERS_READ); perms.add(PosixFilePermission.OTHERS_EXECUTE); Files.setPosixFilePermissions(outputFile, perms); } } }