/*
* 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);
}
}
}