com.sun.enterprise.admin.servermgmt.cli.LocalServerCommand Maven / Gradle / Ivy
Show all versions of payara-micro Show documentation
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2013 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
// Portions Copyright [2016] [Payara Foundation]
package com.sun.enterprise.admin.servermgmt.cli;
import com.sun.enterprise.admin.cli.CLICommand;
import com.sun.enterprise.admin.cli.CLIConstants;
import com.sun.enterprise.admin.cli.ProgramOptions;
import com.sun.enterprise.admin.cli.remote.RemoteCLICommand;
import com.sun.enterprise.util.io.FileUtils;
import java.io.*;
import java.net.*;
import java.util.*;
import java.security.KeyStore;
import org.glassfish.api.ActionReport;
import org.glassfish.api.admin.CommandException;
import com.sun.enterprise.security.store.PasswordAdapter;
import com.sun.enterprise.universal.i18n.LocalStringsImpl;
import com.sun.enterprise.universal.io.SmartFile;
import com.sun.enterprise.universal.process.Jps;
import com.sun.enterprise.universal.process.ProcessUtils;
import com.sun.enterprise.universal.xml.MiniXmlParser;
import com.sun.enterprise.universal.xml.MiniXmlParserException;
import com.sun.enterprise.util.HostAndPort;
import com.sun.enterprise.util.io.ServerDirs;
import java.util.logging.Level;
/**
* A class that's supposed to capture all the behavior common to operation
* on a "local" server.
* It's getting fairly complicated thus the "section headers" comments.
* This class plays two roles, - a place for putting common code - which
* are final methods. A parent class that is communicating with its own unknown
* sub-classes. These are non-final methods
*
* @author Byron Nevins
*/
public abstract class LocalServerCommand extends CLICommand {
////////////////////////////////////////////////////////////////
/// Section: protected methods that are OK to override
////////////////////////////////////////////////////////////////
/**
* Override this method and return false to turn-off the file validation.
* E.g. it demands that config/domain.xml be present. In special cases like
* Synchronization -- this is how you turn off the testing.
* @return true - do the checks, false - don't do the checks
*/
protected boolean checkForSpecialFiles() {
return true;
}
////////////////////////////////////////////////////////////////
/// Section: protected methods that are notOK to override.
////////////////////////////////////////////////////////////////
/**
* Returns the admin address of the local domain. Note that this method
* should be called only when you own the domain that is available on
* an accessible file system.
*
* @return HostAndPort object with admin server address
* @throws CommandException in case of parsing errors
*/
protected final HostAndPort getAdminAddress() throws CommandException {
// default: DAS which always has the name "server"
return getAdminAddress("server");
}
/**
* Returns the admin address of a particular server. Note that this method
* should be called only when you own the server that is available on
* an accessible file system.
*
* @return HostAndPort object with admin server address
* @throws CommandException in case of parsing errors
*/
protected final HostAndPort getAdminAddress(String serverName)
throws CommandException {
try {
MiniXmlParser parser = new MiniXmlParser(getDomainXml(), serverName);
List
addrSet = parser.getAdminAddresses();
if (addrSet.size() > 0)
return addrSet.get(0);
else
throw new CommandException(strings.get("NoAdminPort"));
}
catch (MiniXmlParserException ex) {
throw new CommandException(strings.get("NoAdminPortEx", ex), ex);
}
}
protected final void setServerDirs(ServerDirs sd) {
serverDirs = sd;
}
protected final boolean isLocal() {
return serverDirs != null && serverDirs.getServerName() != null;
}
protected final boolean isRemote() {
return !isLocal();
}
private final void resetLocalPassword() throws IOException {
resetServerDirs();
setLocalPassword();
}
protected final void setLocalPassword() {
String pw = serverDirs == null ? null : serverDirs.getLocalPassword();
if (ok(pw)) {
programOpts.setPassword(pw,
ProgramOptions.PasswordLocation.LOCAL_PASSWORD);
logger.finer("Using local password");
}
else
logger.finer("Not using local password");
}
protected final void unsetLocalPassword() {
programOpts.setPassword(null,
ProgramOptions.PasswordLocation.LOCAL_PASSWORD);
}
protected final void resetServerDirs() throws IOException {
if(serverDirs == null)
throw new RuntimeException(Strings.get("NoServerDirs"));
serverDirs = serverDirs.refresh();
}
protected final ServerDirs getServerDirs() {
return serverDirs;
}
protected final File getDomainXml() {
if(serverDirs == null)
throw new RuntimeException(Strings.get("NoServerDirs"));
return serverDirs.getDomainXml();
}
/**
* Checks if the create-domain was created using --savemasterpassword flag
* which obtains security by obfuscation! Returns null in case of failure
* of any kind.
* @return String representing the password from the JCEKS store named
* master-password in domain folder
*/
protected final String readFromMasterPasswordFile() {
File mpf = getMasterPasswordFile();
if (mpf == null)
return null; // no master password saved
try {
PasswordAdapter pw = new PasswordAdapter(mpf.getAbsolutePath(),
"master-password".toCharArray()); // fixed key
return pw.getPasswordForAlias("master-password");
}
catch (Exception e) {
logger.log(Level.FINER, "master password file reading error: {0}", e.getMessage());
return null;
}
}
protected final boolean verifyMasterPassword(String mpv) {
//issue : 14971, should ideally use javax.net.ssl.keyStore and
//javax.net.ssl.keyStoreType system props here but they are
//unavailable to asadmin start-domain hence falling back to
//cacerts.jks instead of keystore.jks. Since the truststore
//is less-likely to be Non-JKS
return loadAndVerifyKeystore(getJKS(),mpv);
}
protected boolean loadAndVerifyKeystore(File jks,String mpv) {
FileInputStream fis = null;
try {
fis = new FileInputStream(jks);
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(fis, mpv.toCharArray());
return true;
}
catch (Exception e) {
if (logger.isLoggable(Level.FINER))
logger.finer(e.getMessage());
return false;
}
finally {
try {
if (fis != null)
fis.close();
}
catch (IOException ioe) {
// ignore, I know ...
}
}
}
/**
* Get the master password, either from a password file or
* by asking the user.
*/
protected final String getMasterPassword() throws CommandException {
// Sets the password into the launcher info.
// Yes, returning master password as a string is not right ...
final int RETRIES = 3;
long t0 = now();
String mpv = passwords.get(CLIConstants.MASTER_PASSWORD);
if (mpv == null) { //not specified in the password file
mpv = "changeit"; //optimization for the default case -- see 9592
if (!verifyMasterPassword(mpv)) {
mpv = readFromMasterPasswordFile();
if (!verifyMasterPassword(mpv)) {
mpv = retry(RETRIES);
}
}
}
else { // the passwordfile contains AS_ADMIN_MASTERPASSWORD, use it
if (!verifyMasterPassword(mpv))
mpv = retry(RETRIES);
}
long t1 = now();
logger.log(Level.FINER, "Time spent in master password extraction: {0} msec", (t1 - t0)); //TODO
return mpv;
}
/**
* See if the server is alive and is the one at the specified directory.
*
* @return true if it's the DAS at this domain directory
*/
protected final boolean isThisServer(File ourDir, String directoryKey) {
if (!ok(directoryKey))
throw new NullPointerException();
ourDir = getUniquePath(ourDir);
logger.log(Level.FINER, "Check if server is at location {0}", ourDir);
try {
RemoteCLICommand cmd =
new RemoteCLICommand("__locations", programOpts, env);
ActionReport report =
cmd.executeAndReturnActionReport(new String[]{"__locations"});
String theirDirPath = report.findProperty(directoryKey);
logger.log(Level.FINER, "Remote server has root directory {0}", theirDirPath);
if (ok(theirDirPath)) {
File theirDir = getUniquePath(new File(theirDirPath));
return theirDir.equals(ourDir);
}
return false;
}
catch (Exception ex) {
return false;
}
}
protected final int getServerPid() {
try {
return Integer.parseInt(
new RemoteCLICommand("__locations", programOpts, env)
.executeAndReturnActionReport(new String[]{"__locations"})
.findProperty("Pid"));
}
catch (Exception e) {
return -1;
}
}
/**
* There is sometimes a need for subclasses to know if a
* local domain
is running. An example of such a command is
* change-master-password command. The stop-domain command also needs to
* know if a domain is running without having to provide user
* name and password on command line (this is the case when I own a domain
* that has non-default admin user and password) and want to stop it
* without providing it.
*
* In such cases, we need to know if the domain is running and this method
* provides a way to do that.
*
* @return boolean indicating whether the server is running
*/
protected final boolean isRunning(String host, int port) {
Socket server = null;
try {
server = new Socket(host, port);
return true;
}
catch (Exception ex) {
logger.log(Level.FINER, "\nisRunning got exception: {0}", ex);
return false;
}
finally {
if (server != null) {
try {
server.close();
}
catch (IOException ex) {
}
}
}
}
/**
* Is the server still running?
* This is only called when we're hanging around waiting for the server to die.
* Byron Nevins, Nov 7, 2010 - Check to see if the process itself is still running
* We use OS tools to figure this out. See ProcessUtils for details.
* Failover to the JPS check if necessary
*
* bnevins, May 2013
* http://serverfault.com/questions/181015/how-do-you-free-up-a-port-being-held-open-by-dead-process
* In WIndows the admin port may be held open for a while -- if there happens to
* be an attached running child process. This is the key message from the url:
*
* If your program spawned any processes while it was running, try killing them.
* That should cause its process record to be freed and the TCP port to be
* cleaned up. Apparently windows does this when the record is released not
* when the process exits as I would have expected.
*/
protected boolean isRunning() {
int pp = getPrevPid();
if (pp < 0)
return isRunningByCheckingForPidFile();
Boolean b = ProcessUtils.isProcessRunning(pp);
if (b == null) // this means it couldn't find out!
return isRunningUsingJps();
else
return b.booleanValue();
}
/**
* Byron Nevins Says: We have quite a historical assortment of ways to
* determine if a server has restarted. There are little teeny timing issues
* with all of them. I'm confident that this new technique will clear them
* all up. Here we are just monitoring the PID of the new server and
* comparing it to the pid of the old server. The oldServerPid is guaranteed
* to be either the PID of the "old" server or -1 if we couldn't get it --
* or it isn't running. If it is -1 then we make the assumption that once we
* DO get a valid pid that the server has started. If the old pid is valid
* we simply poll until we get a different pid. Notice that we will never
* get a valid pid back unless the server is officially up and running and
* "STARTED" Created April 2013
*
* @param oldServerPid The pid of the server which is being restarted.
* @throws CommandException if we time out.
*/
protected final void waitForRestart(final int oldServerPid) throws CommandException {
long end = getEndTime();
while (now() < end) {
try {
if(isLocal())
resetLocalPassword();
int newServerPid = getServerPid();
if (newServerPid > 0 && newServerPid != oldServerPid) {
logger.log(Level.FINER, "oldserver-pid, newserver-pid = {0} --- {1}",
new Object[]{oldServerPid, newServerPid});
return;
}
Thread.sleep(CLIConstants.RESTART_CHECK_INTERVAL_MSEC);
}
catch (Exception e) {
// continue
}
}
// if we get here -- we timed out
throw new CommandException(strings.get("restartDomain.noGFStart"));
}
// todo move prevpid to ServerDirs ???
protected final int getPrevPid() {
try {
File prevPidFile = new File(getServerDirs().getPidFile().getPath() + ".prev");
if (!prevPidFile.canRead())
return -1;
String pids = FileUtils.readSmallFile(prevPidFile).trim();
return Integer.parseInt(pids);
}
catch (Exception ex) {
return -1;
}
}
/**
* Is the server still running?
* This is only called when we're hanging around waiting for the server to die.
* Byron Nevins, Nov 7, 2010 - Check to see if the process itself is still running
* We use jps to check
* If there are any problems fall back to the previous implementation of
* isRunning() which looks for the pidfile to get deleted
*/
private boolean isRunningUsingJps() {
int pp = getPrevPid();
if (pp < 0)
return isRunningByCheckingForPidFile();
return Jps.isPid(pp);
}
/**
* Is the server still running?
* This is only called when we're hanging around waiting for the server to die.
*/
private boolean isRunningByCheckingForPidFile() {
File pf = getServerDirs().getPidFile();
if (pf != null) {
return pf.exists();
}
else
return isRunning(programOpts.getHost(), // remote case
programOpts.getPort());
}
/**
* Get uptime from the server.
*/
protected final long getUptime() throws CommandException {
RemoteCLICommand cmd = new RemoteCLICommand("uptime", programOpts, env);
String up = cmd.executeAndReturnOutput("uptime", "--milliseconds").trim();
long up_ms = parseUptime(up);
if (up_ms <= 0) {
throw new CommandException(strings.get("restart.dasNotRunning"));
}
logger.log(Level.FINER, "server uptime: {0}", up_ms);
return up_ms;
}
/**
* See if the server is restartable
* As of March 2011 -- this only returns false if a passwordfile argument was given
* when the server started -- but it is no longer available - i.e. the user
* deleted it or made it unreadable.
*/
protected final boolean isRestartable() throws CommandException {
// false negative is worse than false positive.
// there is one and only one case where we return false
RemoteCLICommand cmd = new RemoteCLICommand("_get-runtime-info", programOpts, env);
ActionReport report = cmd.executeAndReturnActionReport("_get-runtime-info");
if (report != null) {
String val = report.findProperty("restartable_value");
if (ok(val) && val.equals("false"))
return false;
}
return true;
}
////////////////////////////////////////////////////////////////
/// Section: private methods
////////////////////////////////////////////////////////////////
/**
* The remote uptime command returns a string like:
* Uptime: 10 minutes, 53 seconds, Total milliseconds: 653859\n
* We find that last number and extract it.
* XXX - this is pretty gross, and fragile
*/
private long parseUptime(String up) {
try {
return Long.parseLong(up);
}
catch (Exception e) {
return 0;
}
}
private File getJKS() {
if (serverDirs == null)
return null;
File mp = new File(new File(serverDirs.getServerDir(), "config"), "cacerts.jks");
if (!mp.canRead())
return null;
return mp;
}
protected File getMasterPasswordFile() {
if (serverDirs == null)
return null;
File mp = new File(serverDirs.getConfigDir(), "master-password");
if (!mp.canRead())
return null;
return mp;
}
private String retry(int times) throws CommandException {
String mpv;
// prompt times times
for (int i = 0; i < times; i++) {
// XXX - I18N
String prompt = strings.get("mp.prompt", (times - i));
mpv = super.readPassword(prompt);
if (mpv == null)
throw new CommandException(strings.get("no.console"));
// ignore retries :)
if (verifyMasterPassword(mpv))
return mpv;
if (i < (times - 1))
logger.info(strings.get("retry.mp"));
// make them pay for typos?
//Thread.currentThread().sleep((i+1)*10000);
}
throw new CommandException(strings.get("mp.giveup", times));
}
private File getUniquePath(File f) {
try {
f = f.getCanonicalFile();
}
catch (IOException ioex) {
f = SmartFile.sanitize(f);
}
return f;
}
private long now() {
// it's just *so* ugly to call this directly!
return System.currentTimeMillis();
}
private long getEndTime() {
// it's a method in case we someday allow configuring this VERY long
// timeout at runtime.
return CLIConstants.WAIT_FOR_DAS_TIME_MS + now();
}
////////////////////////////////////////////////////////////////
/// Section: private variables
////////////////////////////////////////////////////////////////
private ServerDirs serverDirs;
private static final LocalStringsImpl strings =
new LocalStringsImpl(LocalDomainCommand.class);
}