blob: 5a28faed00cbb13f0e4e43afbd060867bf0aa4cf [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional information regarding
* copyright ownership. The ASF 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.apache.geode.management.internal.cli.commands;
import static org.apache.geode.management.internal.cli.i18n.CliStrings.CACHE_XML_NOT_FOUND_MESSAGE;
import static org.apache.geode.management.internal.cli.i18n.CliStrings.CREATE_REGION;
import static org.apache.geode.management.internal.cli.i18n.CliStrings.CREATE_REGION__REGION;
import static org.apache.geode.management.internal.cli.i18n.CliStrings.CREATE_REGION__REGIONSHORTCUT;
import static org.apache.geode.management.internal.cli.i18n.CliStrings.GEODE_0_PROPERTIES_1_NOT_FOUND_MESSAGE;
import static org.apache.geode.management.internal.cli.i18n.CliStrings.GROUP;
import static org.apache.geode.management.internal.cli.i18n.CliStrings.START_SERVER;
import static org.apache.geode.management.internal.cli.i18n.CliStrings.START_SERVER__CACHE_XML_FILE;
import static org.apache.geode.management.internal.cli.i18n.CliStrings.START_SERVER__DIR;
import static org.apache.geode.management.internal.cli.i18n.CliStrings.START_SERVER__FORCE;
import static org.apache.geode.management.internal.cli.i18n.CliStrings.START_SERVER__HOSTNAME__FOR__CLIENTS;
import static org.apache.geode.management.internal.cli.i18n.CliStrings.START_SERVER__LOCATORS;
import static org.apache.geode.management.internal.cli.i18n.CliStrings.START_SERVER__MAXHEAP;
import static org.apache.geode.management.internal.cli.i18n.CliStrings.START_SERVER__NAME;
import static org.apache.geode.management.internal.cli.i18n.CliStrings.START_SERVER__OFF_HEAP_MEMORY_SIZE;
import static org.apache.geode.management.internal.cli.i18n.CliStrings.START_SERVER__PROPERTIES;
import static org.apache.geode.management.internal.cli.i18n.CliStrings.START_SERVER__SECURITY_PROPERTIES;
import static org.apache.geode.management.internal.cli.i18n.CliStrings.START_SERVER__SERVER_PORT;
import static org.apache.geode.management.internal.cli.i18n.CliStrings.START_SERVER__USERNAME;
import static org.apache.geode.test.compiler.ClassBuilder.writeJarFromClasses;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.isA;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Serializable;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.file.Paths;
import java.util.List;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.apache.geode.cache.client.internal.locator.GetAllServersRequest;
import org.apache.geode.cache.client.internal.locator.GetAllServersResponse;
import org.apache.geode.cache.execute.FunctionException;
import org.apache.geode.cache.execute.FunctionService;
import org.apache.geode.distributed.AbstractLauncher;
import org.apache.geode.distributed.ServerLauncher;
import org.apache.geode.distributed.internal.InternalLocator;
import org.apache.geode.distributed.internal.ServerLocation;
import org.apache.geode.distributed.internal.ServerLocator;
import org.apache.geode.internal.AvailablePortHelper;
import org.apache.geode.internal.process.ProcessType;
import org.apache.geode.internal.process.ProcessUtils;
import org.apache.geode.management.internal.cli.i18n.CliStrings;
import org.apache.geode.management.internal.cli.util.CommandStringBuilder;
import org.apache.geode.test.dunit.rules.ClusterStartupRule;
import org.apache.geode.test.dunit.rules.MemberVM;
import org.apache.geode.test.dunit.rules.SharedErrorCollector;
import org.apache.geode.test.junit.rules.GfshCommandRule;
import org.apache.geode.test.junit.rules.RequiresGeodeHome;
import org.apache.geode.test.junit.rules.serializable.SerializableTemporaryFolder;
import org.apache.geode.test.junit.rules.serializable.SerializableTestName;
public class StartServerCommandDUnitTest implements Serializable {
private static MemberVM locator;
private static String locatorConnectionString;
private File workingDir;
private String memberName;
private int serverPort;
@ClassRule
public static RequiresGeodeHome requiresGeodeHome = new RequiresGeodeHome();
@ClassRule
public static ClusterStartupRule cluster = new ClusterStartupRule();
@ClassRule
public static GfshCommandRule gfsh = new GfshCommandRule();
@Rule
public SharedErrorCollector errorCollector = new SharedErrorCollector();
@Rule
public SerializableTemporaryFolder temporaryFolder = new SerializableTemporaryFolder();
@Rule
public SerializableTestName testName = new SerializableTestName();
@BeforeClass
public static void beforeClass() throws Exception {
// locator used to clean up members started during tests
locator = cluster.startLocatorVM(0, 0);
locatorConnectionString = "localhost[" + locator.getPort() + "]";
gfsh.connectAndVerify(locator);
}
@Before
public void before() throws IOException {
workingDir = temporaryFolder.newFolder();
memberName = testName.getMethodName();
serverPort = AvailablePortHelper.getRandomAvailableTCPPort();
}
@AfterClass
public static void afterClass() throws Exception {
gfsh.connectAndVerify(locator);
gfsh.execute("shutdown --include-locators");
}
@Test
public void testWithMissingCacheXml() throws IOException {
String missingCacheXmlPath =
Paths.get("missing", "cache.xml").toAbsolutePath().toString();
String expectedError =
CliStrings.format(CACHE_XML_NOT_FOUND_MESSAGE, missingCacheXmlPath);
String command = new CommandStringBuilder(START_SERVER)
.addOption(START_SERVER__NAME, memberName)
.addOption(START_SERVER__LOCATORS, locatorConnectionString)
.addOption(START_SERVER__SERVER_PORT, String.valueOf(serverPort))
.addOption(START_SERVER__CACHE_XML_FILE, missingCacheXmlPath)
.addOption(START_SERVER__DIR, workingDir.getCanonicalPath())
.getCommandString();
gfsh.executeAndAssertThat(command).statusIsError()
.containsOutput(expectedError);
}
@Test
public void testWithMissingGemFirePropertiesFile() throws IOException {
String missingGemfirePropertiesPath =
Paths.get("missing", "gemfire.properties").toAbsolutePath().toString();
String expectedError =
CliStrings.format(GEODE_0_PROPERTIES_1_NOT_FOUND_MESSAGE, "", missingGemfirePropertiesPath);
String command = new CommandStringBuilder(START_SERVER)
.addOption(START_SERVER__NAME, memberName)
.addOption(START_SERVER__LOCATORS, locatorConnectionString)
.addOption(START_SERVER__SERVER_PORT, String.valueOf(serverPort))
.addOption(START_SERVER__PROPERTIES, missingGemfirePropertiesPath)
.addOption(START_SERVER__DIR, workingDir.getCanonicalPath())
.getCommandString();
gfsh.executeAndAssertThat(command).statusIsError()
.containsOutput(expectedError);
}
@Test
public void testWithMissingPassword() throws IOException {
String expectedError = "password must be specified.";
String command = new CommandStringBuilder(START_SERVER)
.addOption(START_SERVER__NAME, memberName)
.addOption(START_SERVER__LOCATORS, locatorConnectionString)
.addOption(START_SERVER__SERVER_PORT, String.valueOf(serverPort))
.addOption(START_SERVER__USERNAME, "usernameValue")
.addOption(START_SERVER__DIR, workingDir.getCanonicalPath())
.getCommandString();
gfsh.executeAndAssertThat(command).statusIsError()
.containsOutput(expectedError);
}
@Test
public void testWithMissingSecurityPropertiesFile() throws IOException {
String missingSecurityPropertiesPath =
Paths.get("missing", "security.properties").toAbsolutePath().toString();
String expectedError = CliStrings.format(GEODE_0_PROPERTIES_1_NOT_FOUND_MESSAGE,
"Security ", missingSecurityPropertiesPath);
String command = new CommandStringBuilder(START_SERVER)
.addOption(START_SERVER__NAME, memberName)
.addOption(START_SERVER__LOCATORS, locatorConnectionString)
.addOption(START_SERVER__SERVER_PORT, String.valueOf(serverPort))
.addOption(START_SERVER__DIR, workingDir.getCanonicalPath())
.addOption(START_SERVER__SECURITY_PROPERTIES, missingSecurityPropertiesPath)
.getCommandString();
gfsh.executeAndAssertThat(command).statusIsError()
.containsOutput(expectedError);
}
@Test
public void testWithUnavailablePort() throws IOException {
String expectedError =
"java.lang.RuntimeException: An IO error occurred while starting a Server";
String expectedCause = "Caused by: java.net.BindException: "
+ "Network is unreachable; port (" + serverPort + ") is not available on localhost.";
try (Socket interferingProcess = new Socket()) {
// make the target port unavailable
interferingProcess.bind(new InetSocketAddress(serverPort));
String command = new CommandStringBuilder(START_SERVER)
.addOption(START_SERVER__NAME, memberName)
.addOption(START_SERVER__LOCATORS, locatorConnectionString)
.addOption(START_SERVER__SERVER_PORT, String.valueOf(serverPort))
.addOption(START_SERVER__DIR, workingDir.getCanonicalPath())
.getCommandString();
gfsh.executeAndAssertThat(command).statusIsError()
.hasOutput().contains(expectedError, expectedCause);
}
}
@Test
public void testWithAvailablePort() throws IOException {
String expectedMessage = "Server in " + workingDir.getCanonicalPath();
String expectedMessage2 = "as " + memberName + " is currently online.";
String command = new CommandStringBuilder(START_SERVER)
.addOption(START_SERVER__NAME, memberName)
.addOption(START_SERVER__LOCATORS, locatorConnectionString)
.addOption(START_SERVER__SERVER_PORT, String.valueOf(serverPort))
.addOption(START_SERVER__DIR, workingDir.getCanonicalPath())
.getCommandString();
gfsh.executeAndAssertThat(command).statusIsSuccess()
.hasOutput().contains(expectedMessage, expectedMessage2);
}
@Test
public void testWithConflictingPIDFile() throws Exception {
String fileName = ProcessType.SERVER.getPidFileName();
// create PID file
File pidFile = new File(workingDir.getAbsolutePath(), fileName);
assertThat(pidFile.createNewFile()).isTrue();
// write PID to PID file
try (FileWriter fileWriter = new FileWriter(pidFile, false)) {
fileWriter.write(ProcessUtils.identifyPid() + "\n");
fileWriter.flush();
}
assertThat(pidFile.isFile()).isTrue();
String command = new CommandStringBuilder(START_SERVER)
.addOption(START_SERVER__NAME, memberName)
.addOption(START_SERVER__LOCATORS, locatorConnectionString)
.addOption(START_SERVER__SERVER_PORT, String.valueOf(serverPort))
.addOption(START_SERVER__DIR, pidFile.getParentFile().getCanonicalPath())
.getCommandString();
String expectedError = "A PID file already exists and a Server may be running in "
+ pidFile.getParentFile().getCanonicalPath();
String expectedCause = "Caused by: "
+ "org.apache.geode.internal.process.FileAlreadyExistsException: Pid file already exists: "
+ pidFile.getCanonicalPath();
gfsh.executeAndAssertThat(command).statusIsError()
.hasOutput().contains(expectedError, expectedCause);
}
@Test
public void testWithForceOverwriteConflictingPIDFile() throws Exception {
String fileName = ProcessType.SERVER.getPidFileName();
// create PID file
File pidFile = new File(workingDir.getAbsolutePath(), fileName);
assertThat(pidFile.createNewFile()).isTrue();
// write PID to PID file
try (FileWriter fileWriter = new FileWriter(pidFile, false)) {
fileWriter.write(ProcessUtils.identifyPid() + "\n");
fileWriter.flush();
}
assertThat(pidFile.isFile()).isTrue();
String command = new CommandStringBuilder(START_SERVER)
.addOption(START_SERVER__NAME, memberName)
.addOption(START_SERVER__LOCATORS, locatorConnectionString)
.addOption(START_SERVER__SERVER_PORT, String.valueOf(serverPort))
.addOption(START_SERVER__DIR, pidFile.getParentFile().getCanonicalPath())
.addOption(START_SERVER__FORCE, "true")
.getCommandString();
String expectedMessage = "Server in " + pidFile.getParentFile().getCanonicalPath();
gfsh.executeAndAssertThat(command).statusIsSuccess()
.containsOutput(expectedMessage);
}
@Test
public void testWithConnectionToLocator() throws IOException {
String expectedVersionPattern = "Geode Version: \\d+\\.\\d+\\.\\d+";
String expectedMessage = "Server in " + workingDir.getCanonicalPath();
String command = new CommandStringBuilder(START_SERVER)
.addOption(START_SERVER__NAME, memberName)
.addOption(START_SERVER__LOCATORS, locatorConnectionString)
.addOption(START_SERVER__DIR, workingDir.getCanonicalPath())
.addOption(START_SERVER__SERVER_PORT, String.valueOf(serverPort))
.getCommandString();
gfsh.executeAndAssertThat(command).statusIsSuccess()
.hasOutput()
.contains(expectedMessage)
.doesNotContainPattern("Gem[Ff]ire Version")
.containsPattern(expectedVersionPattern);
}
@Test
public void testServerJVMTerminatesOnOutOfMemoryError() throws IOException {
String groupName = "serverGroup";
String regionName = "testRegion";
MemberVM server = cluster.startServerVM(1, locator.getPort());
String command = new CommandStringBuilder(START_SERVER)
.addOption(START_SERVER__NAME, memberName)
.addOption("group", groupName)
.addOption(START_SERVER__LOCATORS, locatorConnectionString)
.addOption(START_SERVER__OFF_HEAP_MEMORY_SIZE, "0M")
.addOption(START_SERVER__MAXHEAP, "300M")
.addOption(START_SERVER__SERVER_PORT, String.valueOf(serverPort))
.addOption(START_SERVER__DIR, workingDir.getCanonicalPath())
.getCommandString();
gfsh.executeAndAssertThat(command).statusIsSuccess();
command = new CommandStringBuilder(CREATE_REGION)
.addOption(CREATE_REGION__REGIONSHORTCUT, "PARTITION")
.addOption(GROUP, groupName)
.addOption(CREATE_REGION__REGION, regionName)
.getCommandString();
gfsh.executeAndAssertThat(command).statusIsSuccess();
ServerLauncher serverLauncher = new ServerLauncher.Builder()
.setCommand(ServerLauncher.Command.STATUS)
.setWorkingDirectory(workingDir.getCanonicalPath())
.build();
assertThat(serverLauncher).isNotNull();
ServerLauncher.ServerState serverState = serverLauncher.status();
assertThat(serverState.getStatus()).isEqualTo(AbstractLauncher.Status.ONLINE);
assertThat(serverState.isVmWithProcessIdRunning()).isTrue();
Integer serverPid = serverState.getPid();
assertThat(ProcessUtils.isProcessAlive(serverPid)).isTrue();
String jarName = "RunOutOfMemory.jar";
File jar = temporaryFolder.newFile(jarName);
writeJarFromClasses(jar, RunOutOfMemoryFunction.class);
gfsh.executeAndAssertThat("deploy --groups=" + groupName + " --jar=" + jar).statusIsSuccess();
locator.invoke((() -> {
try {
FunctionService.onMember(groupName).execute(new RunOutOfMemoryFunction()).getResult();
} catch (FunctionException e) {
errorCollector.checkThat(e, isA(FunctionException.class));
}
}));
gfsh.executeAndAssertThat("list members").statusIsSuccess().doesNotContainOutput(memberName)
.containsOutput(server.getName());
server.stop(Boolean.TRUE);
assertThat(ProcessUtils.isProcessAlive(serverPid)).isFalse();
}
@Test
public void startServerRespectsHostnameForClients() throws IOException {
String expectedMessagePattern = "Server (.*) is currently online";
String command = new CommandStringBuilder(START_SERVER)
.addOption(START_SERVER__NAME, memberName)
.addOption(START_SERVER__LOCATORS, locatorConnectionString)
.addOption(START_SERVER__DIR, workingDir.getCanonicalPath())
.addOption(START_SERVER__SERVER_PORT, String.valueOf(serverPort))
.addOption(START_SERVER__HOSTNAME__FOR__CLIENTS, "fakeServerName")
.getCommandString();
gfsh.executeAndAssertThat(command).statusIsSuccess()
.hasOutput().containsPattern(expectedMessagePattern);
// Verify that the ServerLocation contains the specified hostname-for-clients.
locator.invoke(() -> {
InternalLocator internalLocator = InternalLocator.getLocator();
final ServerLocator serverLocator = internalLocator.getServerLocatorAdvisee();
GetAllServersResponse serversResponse =
(GetAllServersResponse) serverLocator.processRequest(new GetAllServersRequest());
List<ServerLocation> locators = serversResponse.getServers();
assertThat(locators.stream().filter(
serverLocation -> "fakeServerName".equals(serverLocation.getHostName())))
.isNotEmpty().hasSize(1);
});
}
}