ServerController.java revision 2f6d798e90520dd1b83ac30e53838ae6fd41a150
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
* or http://forgerock.org/license/CDDLv1.0.html.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at legal-notices/CDDLv1_0.txt.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information:
* Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
*
* Copyright 2008-2010 Sun Microsystems, Inc.
* Portions Copyright 2011-2015 ForgeRock AS
*/
package org.opends.quicksetup.util;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Map;
import javax.naming.NamingException;
import javax.naming.ldap.InitialLdapContext;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.LocalizableMessageBuilder;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.opends.quicksetup.*;
import org.opends.quicksetup.installer.InstallerHelper;
import org.opends.server.util.SetupUtils;
import org.opends.server.util.StaticUtils;
import com.forgerock.opendj.cli.CliConstants;
import static com.forgerock.opendj.cli.ArgumentConstants.*;
import static com.forgerock.opendj.cli.Utils.*;
import static com.forgerock.opendj.util.OperatingSystem.*;
import static org.opends.admin.ads.util.ConnectionUtils.*;
import static org.opends.messages.QuickSetupMessages.*;
/**
* Class used to manipulate an OpenDS server.
*/
public class ServerController {
private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
private Application application;
private Installation installation;
/**
* Creates a new instance that will operate on <code>application</code>'s
* installation.
* @param application to use for notifications
*/
public ServerController(Application application) {
this(application, application.getInstallation());
}
/**
* Creates a new instance that will operate on <code>application</code>'s
* installation.
* @param installation representing the server instance to control
*/
public ServerController(Installation installation) {
this(null, installation);
}
/**
* Creates a new instance that will operate on <code>installation</code>
* and use <code>application</code> for notifications.
* @param application to use for notifications
* @param installation representing the server instance to control
*/
public ServerController(Application application, Installation installation) {
if (installation == null) {
throw new NullPointerException("installation cannot be null");
}
this.application = application;
this.installation = installation;
}
/**
* This methods stops the server.
*
* @throws org.opends.quicksetup.ApplicationException if something goes wrong.
*/
public void stopServer() throws ApplicationException {
stopServer(false);
}
/**
* This methods stops the server.
*
* @param suppressOutput boolean indicating that ouput to standard output
* streams from the server should be suppressed.
* @throws org.opends.quicksetup.ApplicationException
* if something goes wrong.
*/
public void stopServer(boolean suppressOutput) throws ApplicationException {
stopServer(suppressOutput,false);
}
/**
* This methods stops the server.
*
* @param suppressOutput boolean indicating that ouput to standard output
* streams from the server should be suppressed.
* @param noPropertiesFile boolean indicating if the stopServer should
* be called without taking into account the
* properties file.
* @throws org.opends.quicksetup.ApplicationException
* if something goes wrong.
*/
public void stopServer(boolean suppressOutput,boolean noPropertiesFile)
throws ApplicationException {
if (suppressOutput && !StandardOutputSuppressor.isSuppressed()) {
StandardOutputSuppressor.suppress();
}
if (suppressOutput && application != null)
{
application.setNotifyListeners(false);
}
try {
if (application != null) {
LocalizableMessageBuilder mb = new LocalizableMessageBuilder();
mb.append(application.getFormattedProgress(
INFO_PROGRESS_STOPPING.get()));
mb.append(application.getLineBreak());
application.notifyListeners(mb.toMessage());
}
logger.info(LocalizableMessage.raw("stopping server"));
ArrayList<String> argList = new ArrayList<String>();
argList.add(Utils.getScriptPath(
Utils.getPath(installation.getServerStopCommandFile())));
int size = argList.size();
if (noPropertiesFile)
{
size++;
}
String[] args = new String[size];
argList.toArray(args);
if (noPropertiesFile)
{
args[argList.size()] = "--" + OPTION_LONG_NO_PROP_FILE;
}
ProcessBuilder pb = new ProcessBuilder(args);
Map<String, String> env = pb.environment();
env.put(SetupUtils.OPENDJ_JAVA_HOME, System.getProperty("java.home"));
env.remove(SetupUtils.OPENDJ_JAVA_ARGS);
env.remove("CLASSPATH");
logger.info(LocalizableMessage.raw("Before calling stop-ds. Is server running? "+
installation.getStatus().isServerRunning()));
int stopTries = 3;
while (stopTries > 0)
{
stopTries --;
logger.info(LocalizableMessage.raw("Launching stop command, stopTries left: "+
stopTries));
try
{
logger.info(LocalizableMessage.raw("Launching stop command, argList: "+argList));
Process process = pb.start();
BufferedReader err =
new BufferedReader(
new InputStreamReader(process.getErrorStream()));
BufferedReader out =
new BufferedReader(
new InputStreamReader(process.getInputStream()));
/* Create these objects to resend the stop process output to the
* details area.
*/
new StopReader(err, true);
new StopReader(out, false);
int returnValue = process.waitFor();
int clientSideError =
org.opends.server.protocols.ldap.
LDAPResultCode.CLIENT_SIDE_CONNECT_ERROR;
if (isWindows()
&& (returnValue == clientSideError || returnValue == 0)) {
/*
* Sometimes the server keeps some locks on the files.
* TODO: remove this code once stop-ds returns properly when
* server is stopped.
*/
int nTries = 10;
boolean stopped = false;
for (int i = 0; i < nTries && !stopped; i++) {
logger.trace("waiting for server to stop");
try {
Thread.sleep(5000);
}
catch (Exception ex)
{
// do nothing
}
stopped = !installation.getStatus().isServerRunning();
logger.info(LocalizableMessage.raw(
"After calling stop-ds. Is server running? " + !stopped));
if (stopped) {
break;
}
if (application != null) {
LocalizableMessageBuilder mb = new LocalizableMessageBuilder();
mb.append(application.getFormattedLog(
INFO_PROGRESS_SERVER_WAITING_TO_STOP.get()));
mb.append(application.getLineBreak());
application.notifyListeners(mb.toMessage());
}
}
if (!stopped) {
returnValue = -1;
}
}
if (returnValue == clientSideError) {
if (application != null) {
LocalizableMessageBuilder mb = new LocalizableMessageBuilder();
mb.append(application.getLineBreak());
mb.append(application.getFormattedLog(
INFO_PROGRESS_SERVER_ALREADY_STOPPED.get()));
mb.append(application.getLineBreak());
application.notifyListeners(mb.toMessage());
}
logger.info(LocalizableMessage.raw("server already stopped"));
break;
} else if (returnValue != 0) {
if (stopTries <= 0)
{
/*
* The return code is not the one expected, assume the server
* could not be stopped.
*/
throw new ApplicationException(
ReturnCode.STOP_ERROR,
INFO_ERROR_STOPPING_SERVER_CODE.get(returnValue),
null);
}
} else {
if (application != null) {
application.notifyListeners(application.getFormattedLog(
INFO_PROGRESS_SERVER_STOPPED.get()));
}
logger.info(LocalizableMessage.raw("server stopped"));
break;
}
} catch (Exception e) {
throw new ApplicationException(
ReturnCode.STOP_ERROR, getThrowableMsg(
INFO_ERROR_STOPPING_SERVER.get(), e), e);
}
}
}
finally {
if (suppressOutput)
{
if (StandardOutputSuppressor.isSuppressed())
{
StandardOutputSuppressor.unsuppress();
}
if (application != null)
{
application.setNotifyListeners(true);
}
}
}
}
/**
* This methods starts the server.
*
*@throws org.opends.quicksetup.ApplicationException if something goes wrong.
*/
public void startServer() throws ApplicationException {
startServer(true, false);
}
/**
* This methods starts the server.
* @param suppressOutput boolean indicating that ouput to standard output
* streams from the server should be suppressed.
* @throws org.opends.quicksetup.ApplicationException if something goes wrong.
*/
public void startServer(boolean suppressOutput)
throws ApplicationException
{
startServer(true, suppressOutput);
}
/**
* This methods starts the server.
* @param verify boolean indicating whether this method will attempt to
* connect to the server after starting to verify that it is listening.
* @param suppressOutput indicating that ouput to standard output streams
* from the server should be suppressed.
* @throws org.opends.quicksetup.ApplicationException if something goes wrong.
*/
private void startServer(boolean verify, boolean suppressOutput)
throws ApplicationException
{
if (suppressOutput && !StandardOutputSuppressor.isSuppressed()) {
StandardOutputSuppressor.suppress();
}
if (suppressOutput && application != null)
{
application.setNotifyListeners(false);
}
try {
if (application != null) {
LocalizableMessageBuilder mb = new LocalizableMessageBuilder();
mb.append(application.getFormattedProgress(
INFO_PROGRESS_STARTING.get()));
mb.append(application.getLineBreak());
application.notifyListeners(mb.toMessage());
}
logger.info(LocalizableMessage.raw("starting server"));
ArrayList<String> argList = new ArrayList<String>();
argList.add(Utils.getScriptPath(
Utils.getPath(installation.getServerStartCommandFile())));
argList.add("--timeout");
argList.add("0");
String[] args = new String[argList.size()];
argList.toArray(args);
ProcessBuilder pb = new ProcessBuilder(args);
pb.directory(installation.getBinariesDirectory());
Map<String, String> env = pb.environment();
env.put(SetupUtils.OPENDJ_JAVA_HOME, System.getProperty("java.home"));
env.remove(SetupUtils.OPENDJ_JAVA_ARGS);
// Upgrader's classpath contains jars located in the temporary
// directory that we don't want locked by the directory server
// when it starts. Since we're just calling the start-ds script
// it will figure out the correct classpath for the server.
env.remove("CLASSPATH");
try
{
String startedId = getStartedId();
Process process = pb.start();
BufferedReader err =
new BufferedReader(new InputStreamReader(process.getErrorStream()));
BufferedReader out =
new BufferedReader(new InputStreamReader(process.getInputStream()));
StartReader errReader = new StartReader(err, startedId, true);
StartReader outputReader = new StartReader(out, startedId, false);
int returnValue = process.waitFor();
logger.info(LocalizableMessage.raw("start-ds return value: "+returnValue));
if (returnValue != 0)
{
throw new ApplicationException(ReturnCode.START_ERROR,
INFO_ERROR_STARTING_SERVER_CODE.get(returnValue),
null);
}
if (outputReader.isFinished())
{
logger.info(LocalizableMessage.raw("Output reader finished."));
}
if (errReader.isFinished())
{
logger.info(LocalizableMessage.raw("Error reader finished."));
}
if (!outputReader.startedIdFound() && !errReader.startedIdFound())
{
logger.warn(LocalizableMessage.raw("Started ID could not be found"));
}
// Check if something wrong occurred reading the starting of the server
ApplicationException ex = errReader.getException();
if (ex == null)
{
ex = outputReader.getException();
}
if (ex != null)
{
// This is meaningless right now since we throw
// the exception below, but in case we change out
// minds later or add the ability to return exceptions
// in the output only instead of throwing...
throw ex;
} else if (verify)
{
/*
* There are no exceptions from the readers and they are marked as
* finished. So it seems that everything went fine.
*
* However we can have issues with the firewalls or do not have rights
* to connect or since the startup process is asynchronous we will
* have to wait for the databases and the listeners to initialize.
* Just check if we can connect to the server.
* Try 30 times with an interval of 3 seconds between try.
*/
boolean connected = false;
Configuration config = installation.getCurrentConfiguration();
int port = config.getAdminConnectorPort();
// See if the application has prompted for credentials. If
// not we'll just try to connect anonymously.
String userDn = null;
String userPw = null;
if (application != null) {
userDn = application.getUserData().getDirectoryManagerDn();
userPw = application.getUserData().getDirectoryManagerPwd();
}
if (userDn == null || userPw == null) {
userDn = null;
userPw = null;
}
InitialLdapContext ctx = null;
for (int i=0; i<50 && !connected; i++)
{
String hostName = null;
if (application != null)
{
hostName = application.getUserData().getHostName();
}
if (hostName == null)
{
hostName = "localhost";
}
int dig = i % 10;
if ((dig == 3 || dig == 4) && !"localhost".equals(hostName))
{
// Try with local host. This might be necessary in certain
// network configurations.
hostName = "localhost";
}
if (dig == 5 || dig == 6)
{
// Try with 0.0.0.0. This might be necessary in certain
// network configurations.
hostName = "0.0.0.0";
}
hostName = getHostNameForLdapUrl(hostName);
String ldapUrl = "ldaps://"+hostName+":" + port;
try
{
int timeout = CliConstants.DEFAULT_LDAP_CONNECT_TIMEOUT;
if (application != null && application.getUserData() != null)
{
timeout = application.getUserData().getConnectTimeout();
}
ctx = createLdapsContext(ldapUrl, userDn, userPw, timeout,
null, null, null);
connected = true;
}
catch (NamingException ne)
{
logger.warn(LocalizableMessage.raw("Could not connect to server: "+ne, ne));
}
finally
{
StaticUtils.close(ctx);
}
if (!connected)
{
try
{
Thread.sleep(3000);
}
catch (Throwable t)
{
// do nothing
}
}
}
if (!connected)
{
final LocalizableMessage msg = isWindows()
? INFO_ERROR_STARTING_SERVER_IN_WINDOWS.get(port)
: INFO_ERROR_STARTING_SERVER_IN_UNIX.get(port);
throw new ApplicationException(ReturnCode.START_ERROR, msg, null);
}
}
} catch (IOException | InterruptedException ioe)
{
throw new ApplicationException(
ReturnCode.START_ERROR,
getThrowableMsg(INFO_ERROR_STARTING_SERVER.get(), ioe), ioe);
}
} finally {
if (suppressOutput)
{
if (StandardOutputSuppressor.isSuppressed())
{
StandardOutputSuppressor.unsuppress();
}
if (application != null)
{
application.setNotifyListeners(true);
}
}
}
}
/**
* This class is used to read the standard error and standard output of the
* Stop process.
* <p/>
* When a new log message is found notifies the
* UninstallProgressUpdateListeners of it. If an error occurs it also
* notifies the listeners.
*/
private class StopReader {
private boolean isFirstLine;
/**
* The protected constructor.
*
* @param reader the BufferedReader of the stop process.
* @param isError a boolean indicating whether the BufferedReader
* corresponds to the standard error or to the standard output.
*/
public StopReader(final BufferedReader reader,
final boolean isError) {
final LocalizableMessage errorTag =
isError ?
INFO_ERROR_READING_ERROROUTPUT.get() :
INFO_ERROR_READING_OUTPUT.get();
isFirstLine = true;
Thread t = new Thread(new Runnable() {
@Override
public void run() {
try {
String line = reader.readLine();
while (line != null) {
if (application != null) {
LocalizableMessageBuilder buf = new LocalizableMessageBuilder();
if (!isFirstLine) {
buf.append(application.getProgressMessageFormatter().
getLineBreak());
}
if (isError) {
buf.append(application.getFormattedLogError(
LocalizableMessage.raw(line)));
} else {
buf.append(application.getFormattedLog(
LocalizableMessage.raw(line)));
}
application.notifyListeners(buf.toMessage());
isFirstLine = false;
}
logger.info(LocalizableMessage.raw("server: " + line));
line = reader.readLine();
}
} catch (Throwable t) {
if (application != null) {
LocalizableMessage errorMsg = getThrowableMsg(errorTag, t);
application.notifyListeners(errorMsg);
}
logger.info(LocalizableMessage.raw("error reading server messages",t));
}
}
});
t.start();
}
}
/**
* Returns the LocalizableMessage ID indicating that the server has started.
* @return the LocalizableMessage ID indicating that the server has started.
*/
private String getStartedId()
{
InstallerHelper helper = new InstallerHelper();
return helper.getStartedId();
}
/**
* This class is used to read the standard error and standard output of the
* Start process.
*
* When a new log message is found notifies the ProgressUpdateListeners
* of it. If an error occurs it also notifies the listeners.
*
*/
private class StartReader
{
private ApplicationException ex;
private boolean isFinished;
private boolean startedIdFound;
private boolean isFirstLine;
/**
* The protected constructor.
* @param reader the BufferedReader of the start process.
* @param startedId the message ID that this class can use to know whether
* the start is over or not.
* @param isError a boolean indicating whether the BufferedReader
* corresponds to the standard error or to the standard output.
*/
public StartReader(final BufferedReader reader, final String startedId,
final boolean isError)
{
final LocalizableMessage errorTag =
isError ?
INFO_ERROR_READING_ERROROUTPUT.get() :
INFO_ERROR_READING_OUTPUT.get();
isFirstLine = true;
Thread t = new Thread(new Runnable()
{
@Override
public void run()
{
try
{
String line = reader.readLine();
while (line != null)
{
if (application != null) {
LocalizableMessageBuilder buf = new LocalizableMessageBuilder();
if (!isFirstLine)
{
buf.append(application.getProgressMessageFormatter().
getLineBreak());
}
if (isError)
{
buf.append(application.getFormattedLogError(
LocalizableMessage.raw(line)));
} else
{
buf.append(application.getFormattedLog(
LocalizableMessage.raw(line)));
}
application.notifyListeners(buf.toMessage());
isFirstLine = false;
}
logger.info(LocalizableMessage.raw("server: " + line));
if (line.toLowerCase().contains("=" + startedId))
{
isFinished = true;
startedIdFound = true;
}
line = reader.readLine();
}
} catch (Throwable t)
{
logger.warn(LocalizableMessage.raw("Error reading output: "+t, t));
ex = new ApplicationException(
ReturnCode.START_ERROR,
getThrowableMsg(errorTag, t), t);
}
isFinished = true;
}
});
t.start();
}
/**
* Returns the ApplicationException that occurred reading the Start error
* and output or <CODE>null</CODE> if no exception occurred.
* @return the exception that occurred reading or <CODE>null</CODE> if
* no exception occurred.
*/
public ApplicationException getException()
{
return ex;
}
/**
* Returns <CODE>true</CODE> if the server starting process finished
* (successfully or not) and <CODE>false</CODE> otherwise.
* @return <CODE>true</CODE> if the server starting process finished
* (successfully or not) and <CODE>false</CODE> otherwise.
*/
public boolean isFinished()
{
return isFinished;
}
/**
* Returns <CODE>true</CODE> if the server start Id was found and
* <CODE>false</CODE> otherwise.
* @return <CODE>true</CODE> if the server start Id was found and
* <CODE>false</CODE> otherwise.
*/
public boolean startedIdFound()
{
return startedIdFound;
}
}
}