
oracle.kv.impl.sna.StorageNodeAgent Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of oracle-nosql-server Show documentation
Show all versions of oracle-nosql-server Show documentation
NoSQL Database Server - supplies build and runtime support for the server (store) side of the Oracle NoSQL Database.
The newest version!
/*-
* Copyright (C) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
*
* This file was distributed by Oracle as part of a version of Oracle NoSQL
* Database made available at:
*
* http://www.oracle.com/technetwork/database/database-technologies/nosqldb/downloads/index.html
*
* Please see the LICENSE file included in the top-level directory of the
* appropriate version of Oracle NoSQL Database for a copy of the license and
* additional information.
*/
package oracle.kv.impl.sna;
import static oracle.kv.impl.param.ParameterState.COMMON_MGMT_CLASS;
import java.io.File;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.lang.management.OperatingSystemMXBean;
import java.lang.reflect.Method;
import java.net.BindException;
import java.net.InetSocketAddress;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.ExportException;
import java.rmi.server.UnicastRemoteObject;
import java.security.Security;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import oracle.kv.KVVersion;
import oracle.kv.impl.admin.CommandServiceAPI;
import oracle.kv.impl.admin.param.AdminParams;
import oracle.kv.impl.admin.param.ArbNodeParams;
import oracle.kv.impl.admin.param.BootstrapParams;
import oracle.kv.impl.admin.param.GlobalParams;
import oracle.kv.impl.admin.param.RepNodeParams;
import oracle.kv.impl.admin.param.SecurityParams;
import oracle.kv.impl.admin.param.SecurityParams.KrbPrincipalInfo;
import oracle.kv.impl.admin.param.StorageNodeParams;
import oracle.kv.impl.arb.admin.ArbNodeAdminAPI;
import oracle.kv.impl.async.EndpointGroup.ListenHandle;
import oracle.kv.impl.fault.ProcessFaultHandler;
import oracle.kv.impl.metadata.Metadata;
import oracle.kv.impl.metadata.MetadataInfo;
import oracle.kv.impl.mgmt.MgmtAgent;
import oracle.kv.impl.mgmt.MgmtAgentFactory;
import oracle.kv.impl.mgmt.MgmtUtil;
import oracle.kv.impl.mgmt.jmx.JmxAgent;
import oracle.kv.impl.monitor.AgentRepository;
import oracle.kv.impl.param.DurationParameter;
import oracle.kv.impl.param.LoadParameters;
import oracle.kv.impl.param.Parameter;
import oracle.kv.impl.param.ParameterMap;
import oracle.kv.impl.param.ParameterState;
import oracle.kv.impl.param.ParameterTracker;
import oracle.kv.impl.param.ParameterUtils;
import oracle.kv.impl.param.SizeParameter;
import oracle.kv.impl.rep.admin.RepNodeAdminAPI;
import oracle.kv.impl.security.ConfigurationException;
import oracle.kv.impl.security.SecureProxy;
import oracle.kv.impl.security.login.LoginManager;
import oracle.kv.impl.security.login.LoginUpdater;
import oracle.kv.impl.security.login.TrustedLoginHandler;
import oracle.kv.impl.security.login.TrustedLoginImpl;
import oracle.kv.impl.sna.collector.CollectorService;
import oracle.kv.impl.sna.masterBalance.MasterBalanceManager;
import oracle.kv.impl.sna.masterBalance.MasterBalanceManager.SNInfo;
import oracle.kv.impl.sna.masterBalance.MasterBalanceManagerInterface;
import oracle.kv.impl.test.TestHook;
import oracle.kv.impl.test.TestHookExecute;
import oracle.kv.impl.test.TestStatus;
import oracle.kv.impl.topo.AdminId;
import oracle.kv.impl.topo.ArbNodeId;
import oracle.kv.impl.topo.RepNodeId;
import oracle.kv.impl.topo.ResourceId;
import oracle.kv.impl.topo.StorageNodeId;
import oracle.kv.impl.util.CommandParser;
import oracle.kv.impl.util.ConfigUtils;
import oracle.kv.impl.util.ConfigurableService.ServiceStatus;
import oracle.kv.impl.util.FileNames;
import oracle.kv.impl.util.FileUtils;
import oracle.kv.impl.util.KVThreadFactory;
import oracle.kv.impl.util.ServiceStatusTracker;
import oracle.kv.impl.util.ServiceUtils;
import oracle.kv.impl.util.SnapshotFileUtils;
import oracle.kv.impl.util.SnapshotFileUtils.SnapshotConfigTask;
import oracle.kv.impl.util.SnapshotFileUtils.SnapshotOp;
import oracle.kv.impl.util.SnapshotFileUtils.SnapshotTaskHandler;
import oracle.kv.impl.util.SnapshotFileUtils.UpdateConfigType;
import oracle.kv.impl.util.VersionUtil;
import oracle.kv.impl.util.registry.AsyncRegistryUtils;
import oracle.kv.impl.util.registry.ClientSocketFactory;
import oracle.kv.impl.util.registry.RMISocketPolicy;
import oracle.kv.impl.util.registry.RMISocketPolicy.SocketFactoryPair;
import oracle.kv.impl.util.registry.RegistryUtils;
import oracle.kv.impl.util.registry.RegistryUtils.InterfaceType;
import oracle.kv.impl.util.registry.ServerSocketFactory;
import oracle.kv.impl.util.server.LoggerUtils;
import com.sleepycat.je.rep.utilint.RepUtils;
import com.sleepycat.je.utilint.JVMSystemUtils;
/**
* The class that does the work of the Storage Node Agent (SNA). It is
* mostly controlled by StorageNodeAgentImpl.
*/
public final class StorageNodeAgent {
/* External commands, for "java -jar" usage. */
public static final String START_COMMAND_NAME = "start";
public static final String START_COMMAND_DESC =
"starts StorageNodeAgent (and if configured, store) in kvroot";
public static final String STOP_COMMAND_NAME = "stop";
public static final String STOP_COMMAND_DESC =
"stops StorageNodeAgent and services related to kvroot";
public static final String STATUS_COMMAND_NAME = "status";
public static final String STATUS_COMMAND_DESC =
"status for StorageNodeAgent and services related to kvroot";
public static final String RESTART_COMMAND_NAME = "restart";
public static final String RESTART_COMMAND_DESC =
"combines stop and start commands into one";
public static final String CONFIG_FLAG = "-config";
public static final String DISABLE_SERVICES_FLAG = "-disable-services";
public static final String COMMAND_ARGS =
CommandParser.getRootUsage() + " " +
CommandParser.optional(CONFIG_FLAG + " ") + " " +
CommandParser.optional(DISABLE_SERVICES_FLAG);
public static final String DEFAULT_CONFIG_FILE = "config.xml";
public static final String DEFAULT_SECURITY_DIR = "security";
public static final String DEFAULT_SECURITY_FILE = "security.xml";
/*
* Additional hidden args. The -shutdown flag is added by "java -jar" when
* stop and restart commands are used.
*/
public static final String SHUTDOWN_FLAG = "-shutdown";
public static final String STATUS_FLAG = "-status";
public static final String THREADS_FLAG = "-threads";
public static final String LINK_COMMAND = "ln";
public static final String RESTORE_FROM_SNAPSHOT =
"-restore-from-snapshot";
public static final String UPDATE_CONFIG_FLAG = "-update-config";
public static final String ADDITIONAL_RESTORE_ARGS =
CommandParser.optional(
RESTORE_FROM_SNAPSHOT + " " +
CommandParser.optional(UPDATE_CONFIG_FLAG + " "));
private static final String IBM_VENDOR_PREFIX = "IBM";
/* The disable-services command has been removed */
public static final String DISABLE_SERVICES_COMMAND_NAME =
"disable-services";
public static final String DISABLE_SERVICES_COMMAND_MSG =
", use the -disable-services" +
"\noption with the " + START_COMMAND_NAME + ", " + STOP_COMMAND_NAME +
", or " + RESTART_COMMAND_NAME + " commands";
/* We assume the admin guide's 256MB recommendation is used. */
public static final int SN_ASSUMED_HEAP_MB =
ParameterUtils.applyMinHeapMB(256);
/*
* RMI registry security properties name and required value,
* introduced since Java 8 version 1.8.0_121.
*/
public static final String RMI_REGISTRY_FILTER_NAME =
"sun.rmi.registry.registryFilter";
public static final String[] RMI_REGISTRY_FILTER_REQUIRED = {
"oracle.kv.**", "java.lang.Enum"
};
public static final String RMI_REGISTRY_FILTER_DELIMITER = ";";
private int jsonVersion = -1;
private final StorageNodeAgentImpl snai;
private StorageNodeAgentInterface exportableSnaif;
/*
* Many of these members are technically final once set but they cannot be
* set in the constructor.
*/
private String bootstrapDir;
private String bootstrapFile;
private File securityDir;
private String securityConfigFile;
private BootstrapParams bp;
private SecurityParams sp;
private GlobalParams globalParams;
private File kvRoot;
private File snRoot;
private File kvConfigPath;
private Registry registry;
private String snaName;
private StorageNodeId snid;
private int serviceWaitMillis;
private int repnodeWaitSecs;
private int maxLink;
private int linkExecWaitSecs;
private boolean isWindows;
private Boolean isLoopback;
private boolean isVerbose;
/* Used for configuration restore */
private String restoreSnapshotName;
private UpdateConfigType isUpdateConfig;
/* SNP Information cached for reporting to mgmt. */
int capacity;
int logFileLimit;
int logFileCount;
int numCPUs;
int memoryMB;
private String storageDirectoriesString;
private String rnLogDirectoriesString;
private String adminDirectoryString;
/**
* The service status associated with the SNA. Note that only the following
* subset of states: STARTING, WAITING_FOR_DEPLOY, and RUNNING are
* currently relevant to the SNA.
*/
private boolean createBootstrapAdmin;
private ServiceStatusTracker statusTracker;
private boolean useThreads;
private Logger logger;
private final Map repNodeServices;
private final Map arbNodeServices;
private ServiceManager adminService;
private MonitorAgentImpl monitorAgent;
private MgmtAgent mgmtAgent;
private TrustedLoginImpl trustedLogin;
private SNASecurity snaSecurity;
private CollectorService collectorService;
private final ParameterTracker snParameterTracker;
private final ParameterTracker globalParameterTracker;
/* SNAParser handles the command line arguments when issuing commands to
* the SNA. The parser should be a member of the class so there's greater
* resiliency in having some parsed results even if some of the arguments
* throw errors.
*/
private SNAParser snaParser;
/* The master Balance manager component in the SNA. */
private MasterBalanceManagerInterface masterBalanceManager;
private String customProcessStartupPrefix;
/**
* The listening handle obtained when initiating shared listening
* operations with the dialog layer, or null.
*/
private ListenHandle asyncRegistryListenHandle;
/**
* Test only. restart*Hook exists to "fake" exiting the process at various
* times to test how things go when it is restarted. It works by creating
* a new SNA and shutting down the running one. Note that the two RN hooks
* apply to RNs and ANs.
*/
private TestHook restartRNHook;
private TestHook restartAdminHook;
private TestHook stopRNHook;
/* Hook to inject failures at different points in SN execution */
public static TestHook FAULT_HOOK;
/*
* Test post hook invoked immediately after the MasterBalanceManager is
* shutdown. The hook is static and fires for all SNs. It can be customized
* if it only needs to fire for a specific SN.
*/
static private TestHook mbmPostShutdownHook;
/**
* A constructor that allows the caller to indicate that the bootstrap
* admin service should or should not be started.
*/
StorageNodeAgent(StorageNodeAgentImpl snai, boolean createBootstrapAdmin) {
this.snai = snai;
bootstrapDir = null;
bootstrapFile = null;
securityDir = null;
securityConfigFile = null;
kvRoot = null;
snRoot = null;
kvConfigPath = null;
registry = null;
snaName = GlobalParams.SNA_SERVICE_NAME;
snid = new StorageNodeId(0);
logger = null;
statusTracker = null;
mgmtAgent = null;
useThreads = false;
this.createBootstrapAdmin = createBootstrapAdmin;
isLoopback = null;
snParameterTracker = new ParameterTracker();
globalParameterTracker = new ParameterTracker();
repNodeServices = new HashMap<>();
arbNodeServices = new HashMap<>();
adminService = null;
final String os = System.getProperty("os.name");
if (os.indexOf("Windows") != -1) {
isWindows = true;
} else {
isWindows = false;
}
restoreSnapshotName = null;
isUpdateConfig = UpdateConfigType.UNKNOWN;
}
public MasterBalanceManagerInterface getMasterBalanceManager() {
return masterBalanceManager;
}
void setRNTestHook(TestHook hook) {
restartRNHook = hook;
}
void setStopRNTestHook(TestHook hook) {
stopRNHook = hook;
}
void setAdminTestHook(TestHook hook) {
restartAdminHook = hook;
}
public static void setMBMPostShutdownHook(TestHook
mbmPostShutdownHook) {
StorageNodeAgent.mbmPostShutdownHook = mbmPostShutdownHook;
}
/**
* For testing.
*/
void setRepNodeWaitSecs(int seconds) {
repnodeWaitSecs = seconds;
}
SNAParser getSNAParser () {
return snaParser;
}
class SNAParser extends CommandParser {
private boolean shutdown;
private boolean status;
private boolean disableServices;
private String command = "start SNA";
SNAParser(String[] args) {
super(args);
}
public boolean getShutdown() {
return shutdown;
}
public boolean getStatus() {
return status;
}
public boolean getDisableServices() {
return disableServices;
}
public String getCommand() {
return command;
}
@Override
protected void verifyArgs() {
if (getRootDir() == null) {
missingArg(ROOT_FLAG);
} else {
final String reason =
FileUtils.verifyDirectory(new File(getRootDir()));
if (reason != null) {
final String msg = "Root directory " + reason;
throw new IllegalArgumentException(msg);
}
}
if (bootstrapFile == null) {
bootstrapFile = DEFAULT_CONFIG_FILE;
}
if (securityConfigFile == null) {
securityConfigFile = DEFAULT_SECURITY_FILE;
}
if (restoreSnapshotName == null &&
isUpdateConfig != UpdateConfigType.UNKNOWN) {
usage(UPDATE_CONFIG_FLAG + " can only specify with " +
RESTORE_FROM_SNAPSHOT + " option");
}
isVerbose = getVerbose();
}
@Override
protected boolean checkArg(String arg) {
if (arg.equals(CONFIG_FLAG)) {
bootstrapFile = nextArg(arg);
return true;
}
if (arg.equals(SHUTDOWN_FLAG)) {
command = "stop SNA";
shutdown = true;
return true;
}
if (arg.equals(STATUS_FLAG)) {
command = "status SNA";
status = true;
return true;
}
if (arg.equals(DISABLE_SERVICES_FLAG)) {
command = "stop SNA and disable services";
disableServices = true;
return true;
}
if (arg.equals(THREADS_FLAG)) {
useThreads = true;
return true;
}
if (arg.equals(RESTORE_FROM_SNAPSHOT)) {
restoreSnapshotName = nextArg(arg);
return true;
}
if (arg.equals(UPDATE_CONFIG_FLAG)) {
final String updateConfig = nextArg(arg);
if (updateConfig.equals(Boolean.toString(true))) {
isUpdateConfig = UpdateConfigType.TRUE;
return true;
} else if (updateConfig.equals(Boolean.toString(false))) {
isUpdateConfig = UpdateConfigType.FALSE;
return true;
} else {
usage("Flag " + arg + " requires boolean value");
}
}
return false;
}
@Override
public void usage(String errorMsg) {
throw new IllegalArgumentException
((errorMsg == null ? "" : errorMsg + "\n") +
KVSTORE_USAGE_PREFIX + "\n\t<" +
START_COMMAND_NAME + " | " +
STOP_COMMAND_NAME + " | " +
STATUS_COMMAND_NAME + " | " +
RESTART_COMMAND_NAME + ">\n\t" +
COMMAND_ARGS +
ADDITIONAL_RESTORE_ARGS);
}
}
SNAParser parseArgs(String args[]) {
snaParser = new SNAParser(args);
try {
snaParser.parseArgs();
} catch (IllegalArgumentException re) {
jsonVersion = snaParser.getJsonVersion();
throw re;
}
bootstrapDir = snaParser.getRootDir();
jsonVersion = snaParser.getJsonVersion();
return snaParser;
}
StorageNodeAgentAPI getRunningAgent(int openTimeout, int readTimeout)
throws NotBoundException, RemoteException {
final File configPath = new File(bootstrapDir, bootstrapFile);
logger = LoggerUtils.getBootstrapLogger
(bootstrapDir, FileNames.BOOTSTRAP_SNA_LOG, snaName);
bp = ConfigUtils.getBootstrapParams(configPath, logger);
initSecurity();
sp.initRMISocketPolicies();
/*
* TODO: We probably should be calling snp.setRegistryCSF if
* it is available to us. However, it only affects whether we use
* the default timeouts or configured timeouts, so it's not
* critical.
*/
BootstrapParams.initRegistryCSF(sp);
snaSecurity = new SNASecurity(this, bp, sp, null /* gp */,
null /* snp */, logger);
final StorageNodeAgentAPI snai1;
if (bp.getStoreName() != null) {
StorageNodeId snid1 = new StorageNodeId(bp.getId());
String bn =
RegistryUtils.bindingName(bp.getStoreName(),
snid1.getFullName(),
RegistryUtils.InterfaceType.MAIN);
String csfName =
ClientSocketFactory.factoryName(bp.getStoreName(),
StorageNodeId.getPrefix(),
InterfaceType.MAIN.
interfaceName());
ClientSocketFactory.configureStoreTimeout
(csfName, openTimeout, readTimeout);
snai1 = RegistryUtils.getStorageNodeAgent
(bp.getHostname(), bp.getRegistryPort(), bn,
getLoginManager());
} else {
snai1 = RegistryUtils.getStorageNodeAgent
(bp.getHostname(), bp.getRegistryPort(),
GlobalParams.SNA_SERVICE_NAME, getLoginManager());
}
return snai1;
}
/**
* Using the bootstrap config file, attempt to stop the SNA process that it
* using it. Throws an exception if it fails.
*/
void stopRunningAgent() {
try {
/**
* Set generous open and read timeouts when communicating with
* the SNA to allow time for long RN shutdown times resulting
* from checkpoints.
*/
getRunningAgent(60000, 2 * 60 * 60 *1000).shutdown(true, false);
} catch (RemoteException re) {
throw new IllegalStateException(
"Exception shutting down Storage Node Agent: " +
re.getMessage(),
re);
} catch (NotBoundException nbe) {
throw new IllegalStateException(
"Unable to contact Storage Node Agent: " + nbe.getMessage(),
nbe);
}
}
/**
* Using the bootstrap config file, attempt to get the SNA process status
* Throws an exception if it fails.
*/
ServiceStatus getRunningAgentStatus() {
try {
return getRunningAgent(60000, 5 * 60000).ping().getServiceStatus();
} catch (RemoteException | NotBoundException re) {
return ServiceStatus.UNREACHABLE;
}
}
/**
* Set the securityDir and sp fields, and check that the expected security
* configuration file exists if the configuration calls for it. The parser
* should have been called, and the bp field set, before this method is
* called.
*
* @throws IllegalStateException if a problem is found
*/
private void initSecurity() {
final String relSecurityDir = bp.getSecurityDir();
if (relSecurityDir != null) {
securityDir = new File(bootstrapDir, relSecurityDir);
final File securityConfigPath = new File(securityDir,
securityConfigFile);
if (securityConfigPath.exists()) {
logger.info("Loading security configuration: " +
securityConfigPath);
sp = ConfigUtils.getSecurityParams(securityConfigPath, logger);
} else {
securityDir = null;
throw new IllegalStateException(
"Configuration declares that security should be " +
"present, but it was not found at " +
securityConfigPath);
}
} else {
securityDir = null;
sp = SecurityParams.makeDefault();
}
}
/**
* Start an instance of the Storage Node Agent. It needs to know the
* startup directory and configuration file. The configuration file must
* have this information as well:
* 1. Is the SNA registered or not (kvName is set)
* 2. Initial port for RMI and default service name
* 3. KV root directory
*/
void start()
throws IOException {
/**
* Get a bootstrap logger and initialize status.
*/
logger = LoggerUtils.getBootstrapLogger
(bootstrapDir, FileNames.BOOTSTRAP_SNA_LOG, snaName);
statusTracker = new ServiceStatusTracker(logger);
statusTracker.update(ServiceStatus.STARTING);
checkForConfigRecovery();
final File configPath = new File(bootstrapDir, bootstrapFile);
logger.info("Starting, configuration file: " + configPath);
bp = ConfigUtils.getBootstrapParams(configPath, logger);
setJavaInetAddressProperty(bp);
setRMIRegistryFilterProperty();
try {
initSecurity();
} catch (IllegalStateException e) {
logger.severe(e.getMessage());
throw new IllegalStateException(
e.getMessage() + "\nUnable to continue without security");
}
sp.initRMISocketPolicies();
/*
* Read the version form the boot params and check for an upgrade
* (or downgrade) situation.
*/
final KVVersion previousVersion = bp.getSoftwareVersion();
assert previousVersion != null;
boolean updateConfigFile = false;
if (!previousVersion.equals(KVVersion.CURRENT_VERSION)) {
/* Throws ISE if upgrade cannot be done */
VersionUtil.checkUpgrade(previousVersion);
logger.log(Level.INFO,
"Upgrade software version from version {0} to {1}",
new Object[] {
previousVersion.getNumericVersionString(),
KVVersion.CURRENT_VERSION.getNumericVersionString()
});
bp.setSoftwareVersion(KVVersion.CURRENT_VERSION);
updateConfigFile = true;
}
if (bp.getRootdir() == null) {
bp.setRootdir(bootstrapDir);
updateConfigFile = true;
}
kvRoot = new File(bp.getRootdir());
checkSecurityViolations(previousVersion);
if (updateConfigFile) {
ConfigUtils.createBootstrapConfig(bp, configPath, logger);
}
try {
if (kvRoot.exists() && isRegistered()) {
/* SNA is registered, do registered startup */
startupRegistered();
} else {
/* SNA is not registered, do un-registered startup */
startupUnregistered();
}
} catch (IOException e) {
cleanupRegistry();
throw e;
}
/*
* At this point, both the SNASecurity and TrustedLoginImpl should have
* been initialized, we add them as parameter listeners.
*/
if (sp.isSecure()) {
final LoginUpdater loginUpdater = new LoginUpdater();
loginUpdater.addGlobalParamsUpdaters(snaSecurity);
loginUpdater.addServiceParamsUpdaters(snaSecurity);
if (trustedLogin != null) {
loginUpdater.addGlobalParamsUpdaters(trustedLogin);
loginUpdater.addServiceParamsUpdaters(trustedLogin);
}
snParameterTracker.addListener(
loginUpdater.new ServiceParamsListener());
globalParameterTracker.addListener(
loginUpdater.new GlobalParamsListener());
}
}
private void checkSecurityViolations(KVVersion previousVersion) {
final boolean isSecureStore = (sp != null) && sp.isSecure();
/* Check if a secure registered SN break the security requirement */
if (kvRoot.exists() && isRegistered() && isSecureStore) {
/* initialize store path to find path of storage node config */
initStorePaths();
final boolean isUpgrade =
(!previousVersion.equals(KVVersion.CURRENT_VERSION));
StorageNodeParams snp = null;
try {
snp = ConfigUtils.getStorageNodeParams(kvConfigPath, logger);
} catch (IllegalStateException e) {
throw new IllegalStateException(
"Exception reading config file: " + e.getMessage());
}
/*
* ensure that secure store cannot start up if FTS is in insecure
* mode
*/
if (snp != null) {
final String esCluster = snp.getSearchClusterName();
final boolean esSecured = snp.isSearchClusterSecure();
if (esCluster != null && !esCluster.isEmpty() && !esSecured) {
throw new IllegalStateException(
"Secure store is not allowed if there is a " +
"registered ES cluster " + esCluster + ". " +
(isUpgrade ?
"Please restart the storage node agent with the " +
"previous version (" +
previousVersion.getNumericVersionString() +
") and either configure this secure store as a " +
"non-secure store to register an Elasticsearch " +
"cluster, or first deregister Elasticsearch Cluster" +
" in non secure store, start secure store and " +
"register secure ES Cluster." :
"Please configure this secure store as a " +
"non-secure store and deregister ES Cluster." +
"Then start the secure store and register a " +
"secure Elasticsearch Cluster." ));
}
}
}
}
/**
* Set cache properties for java.net.InetAddress name lookup service.
*
* Two security properties(networkaddress.cache.ttl and
* networkaddress.cache.negative.ttl) control the cache ttl for name
* lookups from the name service. The default behavior is to cache forever
* when a security manager is installed. Such behavior makes system
* unstable when DNS mapping changes. And this affects all processes of
* the system include StorageNodeAgent, RepNode(s), Admin(s) and client
* CLI.
*
* We make the following steps to expose to the user a setter of these
* properties of all the processes:
* (1) Add option "-dns-cachettl" in oracle.kv.impl.util.CommandParser to
* specifiy the ttl. This option controls both positive and negative ttl.
* (2) The oracle.kv.impl.admin.client.CommandShell will use the option
* from (1) to set the process properties of client CLI.
* (3) The oracle.kv.impl.util.KVStoreMain$makeBootConfig will use the
* option from (1) to set the bootstrap parameter.
* (4) When an SNA starts up, the BootstrapParams contains the ttl value.
* The process property is then set accordingly in the method
* setJavaInetAddressProperty().
* (5) When SNA creates child processes, e.g., RepNode and Admin, the
* oracle.kv.impl.sna.ProcessServiceManager makes the child inherit the
* option by setting "-D" option in command line.
* (6) The processes of oracle.kv.impl.sna.ManagedService sets the property
* by reading the system property set from (5).
*
*/
private void setJavaInetAddressProperty(BootstrapParams bp) {
int ttl = bp.getDnsCacheTTL();
Security.setProperty("networkaddress.cache.ttl" ,
Integer.toString(ttl));
Security.setProperty("networkaddress.cache.negative.ttl" ,
Integer.toString(ttl));
System.setProperty("kvdns.networkaddress.cache.ttl",
Integer.toString(ttl));
logger.info("Setting java.net.InetAddress cache ttl to:" +
" networkaddress.cache.ttl=" +
Security.getProperty("networkaddress.cache.ttl") +
" networkaddress.cache.negative.ttl=" +
Security.getProperty("networkaddress.cache.negative.ttl"));
}
/**
* Set required patterns for RMI registry filter.
*
* Since Java 8u121, RMI registry filter sun.rmi.registry.registryFilter is
* enabled by default if using SSL and RMI, which is configured as a
* sequence of patterns. It is required to add our filter patterns,
* so our serialized classes won't be rejected by RMI registry. If there
* is an existing filter pattern set in the system or security property,
* add our required filter patterns to the existing set.
*/
void setRMIRegistryFilterProperty() {
final String existingSecProperty =
Security.getProperty(RMI_REGISTRY_FILTER_NAME);
final String existingSysProperty =
System.getProperty(RMI_REGISTRY_FILTER_NAME);
/*
* If users configured filter patterns in system property, the patterns
* specified in security property won't take effect. For simplicity,
+ * always add our required patterns to filter set in system property.
*/
String existingPatterns = null;
if (existingSysProperty != null) {
existingPatterns = existingSysProperty;
} else if (existingSecProperty != null) {
existingPatterns = existingSecProperty;
}
String patternSet = getRMIFilterSet(existingPatterns);
if (patternSet != null) {
System.setProperty(RMI_REGISTRY_FILTER_NAME, patternSet);
}
/*
* Get system property first, if null, means it's not setting filter
* in system property so the filter patterns in security property
* should be applied on this node.
*/
patternSet = System.getProperty(RMI_REGISTRY_FILTER_NAME);
if (patternSet == null) {
patternSet = existingSecProperty;
}
logger.info("RMI registry serial filter is configured as: " +
RMI_REGISTRY_FILTER_NAME + "=" + patternSet);
}
/**
* Get RMI registry filter patterns. If specified property is null, return
* required filter patterns. If it is not null, check if required patterns
* exist in property value, if not, add them to the existing patterns. If
* existing patterns contains required filter patterns, return null.
*/
String getRMIFilterSet(String existingPatterns) {
final String requiredPatterns =
Arrays.stream(RMI_REGISTRY_FILTER_REQUIRED).
collect(Collectors.joining(RMI_REGISTRY_FILTER_DELIMITER));
if (existingPatterns == null) {
return requiredPatterns;
}
final List patterns = Arrays.asList(
existingPatterns.split(RMI_REGISTRY_FILTER_DELIMITER));
if (!Arrays.stream(RMI_REGISTRY_FILTER_REQUIRED).parallel().
allMatch(s -> patterns.contains(s))) {
return requiredPatterns +
RMI_REGISTRY_FILTER_DELIMITER +
existingPatterns;
}
return null;
}
/**
* Available for testing. During testing, we allow the SNA to cohabitate
* with the client, and the client may alter the socket policies. This
* resets them to standard configuration.
*/
public void resetRMISocketPolicies() {
sp.initRMISocketPolicies();
if (isRegistered()) {
StorageNodeParams snp =
ConfigUtils.getStorageNodeParams(kvConfigPath, logger);
snp.setRegistryCSF(sp);
} else {
BootstrapParams.initRegistryCSF(sp);
}
}
private void logwarning(String msg, Exception e) {
logger.log(Level.WARNING, msg, e);
}
private void logsevere(String msg, Exception e) {
logger.log(Level.SEVERE, msg, e);
}
private void revertToBootstrap() {
try {
File configPath = new File(bootstrapDir, bootstrapFile);
bp.setStoreName(null);
bp.setHostingAdmin(false);
bp.setId(1);
ConfigUtils.createBootstrapConfig(bp, configPath);
snaName = GlobalParams.SNA_SERVICE_NAME;
snid = new StorageNodeId(0);
} catch (Exception e) {
logsevere("Cannot revert to bootstrap configuration", e);
throw new IllegalStateException(e);
}
}
/**
* Unregistered startup:
* 1. Create a default registry
* 2. Kill any running processes (bootstrap admin).
* 3. Start up a bootstrap admin instance
*/
private void startupUnregistered()
throws IOException {
registry = createRegistry(null);
BootstrapParams.initRegistryCSF(sp);
createAsyncRegistry(null);
snaSecurity = new SNASecurity(this, bp, sp, null /* gp */,
null /* snp */, logger);
bindUnregisteredSNA();
bindUnregisteredTrustedLogin();
/*
* Start the mgmt agent AFTER the RMI registry exists. The JMX
* implementation uses this registry.
*/
mgmtAgent = MgmtAgentFactory.getAgent(this, null, statusTracker);
capacity = bp.getCapacity();
numCPUs = bp.getNumCPUs();
memoryMB = bp.getMemoryMB();
storageDirectoriesString = joinStringList(bp.getStorageDirPaths(), ",");
rnLogDirectoriesString = joinStringList(bp.getRNLogDirPaths(), ",");
adminDirectoryString = joinStringList(bp.getAdminDirPath(), ",");
/**
* The fault handler needs a logger in the event an exception occurs
* prior to, or during registration.
*/
snai.getFaultHandler().setLogger(logger);
/**
* If restarted without being registered the bootstrap admin needs to
* be killed if present, otherwise it will fail to start up because its
* http port will be in use. In the event that there is a new config
* file with a different port also match based on kvhome and configfile
* name.
*/
ManagedService.killManagedProcesses
(getStoreName(), makeBootstrapAdminName(), getLogger());
ManagedService.killManagedProcesses
(bootstrapDir, bootstrapFile, getLogger());
startBootstrapAdmin();
statusTracker.update(ServiceStatus.WAITING_FOR_DEPLOY);
}
private synchronized void startupRegistered()
throws IOException {
initStorePaths();
logger.info("Registered startup, config file: " + kvConfigPath);
StorageNodeParams snp = null;
GlobalParams gp = null;
try {
snp = ConfigUtils.getStorageNodeParams(kvConfigPath, logger);
gp = ConfigUtils.getGlobalParams(kvConfigPath, logger);
} catch (IllegalStateException e) {
logger.info("Exception reading config file: " + e.getMessage());
}
if (snp == null || gp == null) {
logger.info("Could not get required parameters, reverting to " +
"unregistered state");
revertToBootstrap();
cleanupRegistry();
/* This is a recursive call */
start();
return;
}
globalParams = gp;
/* Verify the SN ParameterMap before startup any service. */
try {
checkSNParams(snp.getMap(), gp.getMap());
} catch (IllegalArgumentException e) {
logger.severe(e.toString());
}
try {
checkGlobalParams(gp.getMap(), snp.getMap());
} catch (IllegalArgumentException e) {
logger.severe(e.toString());
}
/**
* Set up monitoring and reset logger to use the real snid. Monitor
* must be set up before the logger is created because the
* AgentRepository registers itself in LoggerUtils.
*/
setupMonitoring(gp, snp);
logger.info("Changing log files to directory: " +
FileNames.getLoggingDir(kvRoot, getStoreName()));
logger = LoggerUtils.getLogger(StorageNodeAgentImpl.class, gp, snp);
snai.getFaultHandler().setLogger(logger);
snaSecurity = new SNASecurity(this, bp, sp, gp, snp, logger);
/*
* Any socket timeouts observed by the ClientSocketFactory in this
* process will be logged to this logger.
*/
ClientSocketFactory.setTimeoutLogger(logger);
logger.info("Starting StorageNodeAgent for " + getStoreName());
statusTracker.setLogger(logger);
/**
* The Registry is required for services to start.
*/
if (registry == null) {
registry = createRegistry(snp);
}
if (asyncRegistryListenHandle == null) {
createAsyncRegistry(snp);
}
/**
* Initialize state from parameters.
*/
snid = snp.getStorageNodeId();
JmxAgent.setRMISocketPolicy(sp.getRMISocketPolicy());
initSNParams(snp);
/* Set up the CSF that will be used to access services. */
snp.setRegistryCSF(sp);
snai.startTestInterface();
/**
* Kill leftover managed processes and start new ones.
*/
cleanupRunningComponents();
/*
* Bind the Trusted Login interface so that components can access it
* when they start up.
*/
RegistryUtils.unbind(getHostname(), getRegistryPort(),
"SNA:" + InterfaceType.TRUSTED_LOGIN,
trustedLogin);
bindRegisteredTrustedLogin(gp, snp);
try {
/*
* Note: when security is enabled and mgmtClass is JmxAgent, the
* default client socket factory will be initialized with SNA
* store truststore in JmxAgent and used by collector service.
*/
collectorService = new CollectorService(snp, gp, sp,
getLoginManager(),
logger);
globalParameterTracker.addListener(
collectorService.getGlobalParamsListener());
snParameterTracker.addListener(
collectorService.getSNParamsListener());
logger.info("Create collector service and register parameter "
+ "listeners successfully.");
} catch (Exception e) {
logger.severe("Failed to create collector service: " +
LoggerUtils.getStackTrace(e));
}
/*
* Must precede the startup of the RN components, so their state can be
* tracked.
*/
startMasterBalanceManager(snp.getMasterBalance());
startComponents();
monitorAgent.startup();
/**
* Rebind using new name. Unbind, change name, rebind.
*/
RegistryUtils.unbind(getHostname(), getRegistryPort(), snaName,
exportableSnaif);
snaName = snid.getFullName();
bindRegisteredSNA(snp);
statusTracker.update(ServiceStatus.RUNNING);
if (adminService != null) {
adminService.registered(this);
}
logger.info("Started StorageNodeAgent for " + getStoreName());
}
private void bindUnregisteredSNA()
throws RemoteException {
final RMISocketPolicy policy = sp.getRMISocketPolicy();
final SocketFactoryPair sfp = bp.getStorageNodeAgentSFP(policy);
initExportableSnaif();
if (sfp.getServerFactory() != null) {
sfp.getServerFactory().setConnectionLogger(logger);
}
RegistryUtils.rebind(getHostname(), getRegistryPort(), snaName,
exportableSnaif,
sfp.getClientFactory(),
sfp.getServerFactory());
logger.info("Bound to registry port " + getRegistryPort() +
" using name " + snaName +
" with SSF:" + sfp.getServerFactory());
}
private void bindUnregisteredTrustedLogin()
throws RemoteException {
final RMISocketPolicy trustedPolicy = sp.getTrustedRMISocketPolicy();
if (trustedPolicy != null) {
final SocketFactoryPair tsfp =
bp.getSNATrustedLoginSFP(trustedPolicy);
final String snaTLName = GlobalParams.SNA_LOGIN_SERVICE_NAME;
final SNAFaultHandler faultHandler = new SNAFaultHandler(logger);
final TrustedLoginHandler loginHandler =
new TrustedLoginHandler(snid, true /* localId */);
trustedLogin = new TrustedLoginImpl(faultHandler, loginHandler,
logger);
if (tsfp.getServerFactory() != null) {
tsfp.getServerFactory().setConnectionLogger(logger);
}
RegistryUtils.rebind(getHostname(), getRegistryPort(),
snaTLName,
trustedLogin, tsfp.getClientFactory(),
tsfp.getServerFactory());
logger.info("Bound trusted login to registry port " +
getRegistryPort() + " using name " + snaTLName +
" with SSF:" + tsfp.getServerFactory());
}
}
private void bindRegisteredSNA(StorageNodeParams snp)
throws RemoteException {
final String csfName =
ClientSocketFactory.factoryName(getStoreName(),
StorageNodeId.getPrefix(),
RegistryUtils.InterfaceType.
MAIN.interfaceName());
final RMISocketPolicy rmiPolicy = sp.getRMISocketPolicy();
final SocketFactoryPair sfp =
snp.getStorageNodeAdminSFP(rmiPolicy, csfName);
initExportableSnaif();
if (sfp.getServerFactory() != null) {
sfp.getServerFactory().setConnectionLogger(logger);
}
RegistryUtils.rebind(getHostname(), getRegistryPort(), getStoreName(),
snaName, RegistryUtils.InterfaceType.MAIN,
exportableSnaif,
sfp.getClientFactory(), sfp.getServerFactory());
logger.info("Rebound to registry port " + getRegistryPort() +
" using name " + snaName + " with SSF:" +
sfp.getServerFactory());
}
private void bindRegisteredTrustedLogin(GlobalParams gp,
StorageNodeParams snp)
throws RemoteException {
final RMISocketPolicy trustedPolicy =
sp.getTrustedRMISocketPolicy();
if (trustedPolicy != null) {
final SocketFactoryPair tsfp =
bp.getSNATrustedLoginSFP(trustedPolicy);
final SNAFaultHandler faultHandler = new SNAFaultHandler(logger);
final long sessionTimeout =
gp.getSessionTimeoutUnit().toMillis(gp.getSessionTimeout());
final int sessionLimit = snp.getSessionLimit();
final TrustedLoginHandler loginHandler =
new TrustedLoginHandler(snid, false /* localId */,
sessionTimeout, sessionLimit);
final String snaTLName = GlobalParams.SNA_LOGIN_SERVICE_NAME;
trustedLogin = new TrustedLoginImpl(
faultHandler, loginHandler, logger);
if (tsfp.getServerFactory() != null) {
tsfp.getServerFactory().setConnectionLogger(logger);
}
RegistryUtils.rebind(getHostname(), getRegistryPort(),
snaTLName,
trustedLogin, tsfp.getClientFactory(),
tsfp.getServerFactory());
logger.info("Bound trusted login to registry port " +
getRegistryPort() + " using name " + "SNA" +
" with SSF:" + tsfp.getServerFactory());
}
}
void checkRegistered(String method)
throws IllegalStateException {
if (getStoreName() == null) {
throw new IllegalStateException
(method + ": Storage Node Agent is not registered");
}
}
private void initExportableSnaif() {
try {
exportableSnaif =
SecureProxy.create(snai, snaSecurity.getAccessChecker(),
snai.getFaultHandler());
logger.info(
"Successfully created secure proxy for the storage node agent");
} catch (ConfigurationException ce) {
throw new IllegalStateException("Unabled to create proxy", ce);
}
}
@SuppressWarnings("unused")
private StorageNodeAgentImpl getImpl() {
return snai;
}
StorageNodeStatus getStatus() {
return new StorageNodeStatus(statusTracker.getServiceStatus());
}
MonitorAgentImpl getMonitorAgent() {
return monitorAgent;
}
/**
* Initialize store variables.
*/
private File initStorePaths() {
final File kvDir =
FileNames.getKvDir(kvRoot.toString(), getStoreName());
final StorageNodeId id = new StorageNodeId(bp.getId());
snRoot = FileNames.getStorageNodeDir(kvDir, id);
kvConfigPath =
FileNames.getSNAConfigFile(kvRoot.toString(), getStoreName(), id);
return kvDir;
}
/**
* Ensure that the store directory exists and has a security policy file.
*/
private void ensureStoreDirectory() {
final File kvDir = initStorePaths();
if (!snRoot.isDirectory()) {
if (FileNames.makeDir(snRoot)) {
logger.info("Created a new store directory: " + snRoot);
}
}
/**
* Make sure there's a Java security policy file and if not, copy it
* from the bootstrap directory. This file goes into the store
* directory.
*/
final File javaSecPolicy = FileNames.getSecurityPolicyFile(kvDir);
if (!javaSecPolicy.exists()) {
logger.log(Level.FINE, "Creating security policy file: {0}",
javaSecPolicy);
final File fromFile =
new File(bootstrapDir, FileNames.JAVA_SECURITY_POLICY_FILE);
if (!fromFile.exists()) {
throw new IllegalStateException
("Cannot find bootstrap security file " + fromFile);
}
try {
FileUtils.copyFile(fromFile, javaSecPolicy);
} catch (IOException ie) {
throw new IllegalStateException
("Could not create policy file", ie);
}
}
}
protected Registry getRegistry() {
return registry;
}
public int getRegistryPort() {
return bp.getRegistryPort();
}
public String getServiceName() {
return snaName;
}
public StorageNodeId getStorageNodeId() {
return snid;
}
/**
* These next few are for testing purposes.
*/
protected int getServiceWaitMillis() {
return serviceWaitMillis;
}
protected int getRepnodeWaitSecs() {
return repnodeWaitSecs;
}
protected int getMaxLink() {
return maxLink;
}
protected int getLinkExecWaitSecs() {
return linkExecWaitSecs;
}
/**
* Create a Registry if not already done. Because the SNA does not change
* the registry port when it is "registered" the bootstrap registry can
* remain unmodified.
* @param snp
*/
@SuppressWarnings("null")
private Registry createRegistry(StorageNodeParams snp)
throws RemoteException {
/*
* Initialize the endpoint group first, since it is needed to create
* shared server sockets.
*/
final int numEndpointGroupThreads =
/* No parameters -- just use the minimum number of threads */
(snp == null) ?
Integer.parseInt(
ParameterState.SN_ENDPOINT_GROUP_THREADS_FLOOR_DEFAULT) :
/*
* Running services in processes -- use the minimum number of
* threads for the endpoint group since this process will only host
* the SNA
*/
(!useThreads) ?
snp.getEndpointGroupThreadsFloor() :
/*
* Running services in threads in this process -- use the number of
* threads appropriate for an RN
*/
snp.calcEndpointGroupNumThreads();
AsyncRegistryUtils.initEndpointGroup(
logger, numEndpointGroupThreads, false /* forClient */);
/* Set the server hostname for remote objects */
AsyncRegistryUtils.setServerHostName(getHostname());
final RMISocketPolicy rmiPolicy = sp.getRMISocketPolicy();
final SocketFactoryPair sfp = (snp == null) ?
StorageNodeParams.getDefaultRegistrySFP(rmiPolicy) :
snp.getRegistrySFP(rmiPolicy);
final ServerSocketFactory ssf = sfp.getServerFactory();
logger.info("Creating a Registry on port " +
getHostname() + ":" + getRegistryPort() +
" server socket factory:" + ssf);
/* Note that no CSF is supplied. */
ExportException throwEE = null ;
/* A little over 2 min (the CLOSE_WAIT timeout.) */
final int limitMs = 128000;
final int retryPeriodMs = 1000;
for (int totalWaitMs = 0; totalWaitMs <= limitMs;
totalWaitMs += retryPeriodMs) {
try {
throwEE = null;
return LocateRegistry.createRegistry(getRegistryPort(),
null, ssf);
} catch (ExportException ee) {
throwEE = ee;
if (TestStatus.isActive() &&
(ee.getCause() instanceof BindException)) {
logger.info("Registry bind exception:" +
ee.getCause().getMessage() +
" Registry port:" + getRegistryPort());
try {
Thread.sleep(retryPeriodMs);
} catch (InterruptedException e) {
throw throwEE;
}
continue;
}
throw throwEE;
}
}
if (TestStatus.isActive() &&
(throwEE.getCause() instanceof BindException)) {
/*
* Log information to help identify the process currently binding
* the required port.
*/
/*
* Log all java processes and their args. Assumes jps is available
* on the search path.
*/
logger.info(RepUtils.exec("jps", "-v"));
/*
* Log all processes binding tcp ports. Note that the args are
* linux-specific.
*/
logger.info(RepUtils.exec("netstat", "-lntp"));
}
/* Timed out after retries in test. */
throw throwEE;
}
private void cleanupRegistry() {
/**
* Unbind this object, trusted login if it exists, and clean up
* registry. Don't throw.
*/
try {
if (isRegistered()) {
RegistryUtils.unbind(getHostname(), getRegistryPort(),
GlobalParams.SNA_LOGIN_SERVICE_NAME,
trustedLogin);
RegistryUtils.unbind(getHostname(), getRegistryPort(),
getStoreName(), snaName,
RegistryUtils.InterfaceType.MAIN,
exportableSnaif);
if (monitorAgent != null) {
monitorAgent.stop();
}
snai.stopTestInterface();
} else {
try {
RegistryUtils.unbind(getHostname(), getRegistryPort(),
GlobalParams.SNA_LOGIN_SERVICE_NAME,
trustedLogin);
RegistryUtils.unbind(getHostname(), getRegistryPort(),
snaName, exportableSnaif);
} catch (RemoteException re) {
/* ignore */
}
}
if (registry != null) {
UnicastRemoteObject.unexportObject(registry, true);
}
} catch (Exception ignored) {
} finally {
registry = null;
}
cleanupAsyncRegistry();
}
private void createAsyncRegistry(StorageNodeParams snp)
throws IOException {
if (!AsyncRegistryUtils.serverUseAsync) {
return;
}
final RMISocketPolicy rmiPolicy = sp.getRMISocketPolicy();
final SocketFactoryPair sfp = (snp == null) ?
StorageNodeParams.getDefaultRegistrySFP(rmiPolicy) :
snp.getRegistrySFP(rmiPolicy);
final ServerSocketFactory ssf = sfp.getServerFactory();
asyncRegistryListenHandle = AsyncRegistryUtils.createRegistry(
getHostname(), getRegistryPort(), ssf, logger);
}
private void cleanupAsyncRegistry() {
if (asyncRegistryListenHandle != null) {
try {
asyncRegistryListenHandle.shutdown(true);
} catch (IOException e) {
}
asyncRegistryListenHandle = null;
}
}
/*
* Set system information:
* 1. The number of CPUs based on reality
* 2. If available, the amount of real memory in the system
* NOTE: if using threads for managing services setting memoryMB can
* result in over-allocation of RepNode parameters (e.g. je.maxMemory)
* without direct control over the corresponding Java heap, so in threads
* mode do not set memoryMB.
*/
private void setSystemInfo() {
OperatingSystemMXBean bean =
ManagementFactory.getOperatingSystemMXBean();
logger.info("System architecture is " + bean.getArch());
int ncpu = bp.getNumCPUs();
if (ncpu == 0) {
ncpu = bean.getAvailableProcessors();
logger.info("Setting number of CPUs to " + ncpu);
bp.setNumCPUs(ncpu);
}
/*
* Don't set memoryMB for threads, or in unit tests where we use many
* RNs on a single machine.
*/
if ((!useThreads) && !TestStatus.manyRNs()) {
int mb = bp.getMemoryMB();
if (mb == 0) {
long bytes = JVMSystemUtils.ZING_JVM ?
JVMSystemUtils.getSystemZingMemorySize() :
getTotalPhysicalMemorySize(bean);
if (bytes != 0) {
mb = (int) (bytes >> 20);
logger.info("Setting memory MB to " + mb);
bp.setMemoryMB(mb);
} else {
logger.info("Cannot get memory size");
}
}
}
}
/**
* Get the get the total physical memory available on the machine if the
* mbean implements the getTotalPhysicalMemorySize method (the Sun mbean
* does). If the mbean does not implement the method, it returns 0.
* It is a little more complicated than above... if running a 32-bit JVM
* and capacity 1 the memoryMB should not be > 2G or the JVM will fail to
* start. Logic is added for this situation.
*/
private long getTotalPhysicalMemorySize(OperatingSystemMXBean bean) {
Class> beanClass = bean.getClass();
try {
final int maxInt = Integer.MAX_VALUE;
final String jvmVendor = System.getProperty("java.vendor");
final Method m;
if (jvmVendor != null && jvmVendor.startsWith(IBM_VENDOR_PREFIX)) {
m = beanClass.getMethod("getTotalPhysicalMemory");
} else {
/*
* Specify the interface explicitly so we call the interface
* method, not the class method, because it is the interface
* that is exported by the jdk.management module.
*/
beanClass =
Class.forName("com.sun.management.OperatingSystemMXBean");
m = beanClass.getMethod("getTotalPhysicalMemorySize");
}
m.setAccessible(true); /* Since it's a native method. */
long mem = (Long)m.invoke(bean);
/*
* This call will work because if the above worked we are likely
* using a Sun JVM.
*/
String bits = System.getProperty("sun.arch.data.model");
if (bits == null) {
return mem;
}
int intBits = Integer.parseInt(bits);
if (intBits == 32) {
int cap = bp.getCapacity();
if (mem / cap > maxInt) {
logger.info("Reducing total memory from " +
mem + " to " + (maxInt * cap) + " bytes");
mem = maxInt * cap;
}
}
return mem;
} catch (Exception e) {
logger.warning("Unable to obtain physical memory size: " + e);
return 0;
}
}
/**
* Provide a shutdown hook so that if the SNA is killed externally it can
* attempt to cleanly shut down managed services. There is no reason to
* unbind from the RMI Registry since the process is exiting. This hook
* will only be installed for registered SNA instances.
*
* NOTE: if/when we allow RepNodes and Admin instances to stay up and
* re-register themselves if the SNA dies, this code must change.
*
* NOTE: the Logger instance used here may already be shut down and the
* information not logged.
*/
private class ShutdownThread extends Thread {
@Override
public void run() {
/* if statusTracker is null there is nothing to shut down */
if ((statusTracker != null) &&
((statusTracker.getServiceStatus() == ServiceStatus.RUNNING) ||
(statusTracker.getServiceStatus() ==
ServiceStatus.WAITING_FOR_DEPLOY))) {
logger.info("Shutdown thread running, stopping services");
try {
shutdown(true, false);
} finally {
logger.info("Shutdown thread exiting");
}
}
}
}
void addShutdownHook() {
if (logger != null) {
logger.fine("Adding shutdown hook");
}
Runtime.getRuntime().addShutdownHook(new ShutdownThread());
}
public BootstrapParams getBootstrapParams() {
return bp;
}
public String getStoreName() {
return bp.getStoreName();
}
public String getHostname() {
return bp.getHostname();
}
public String getHAHostname() {
return bp.getHAHostname();
}
boolean isLoopbackAddress() {
if (isLoopback == null) {
isLoopback = checkLoopback(getHAHostname());
}
return isLoopback;
}
public String getHAPortRange() {
return bp.getHAPortRange();
}
public String getServicePortRange() {
return bp.getServicePortRange();
}
public Logger getLogger() {
return logger;
}
public String getBootstrapDir() {
return bootstrapDir;
}
public String getBootstrapFile() {
return bootstrapFile;
}
public File getSecurityDir() {
return securityDir;
}
public String getSecurityConfigFile() {
return securityConfigFile;
}
/**
* Return Kerberos principal configuration information from security
* parameter.
*/
KrbPrincipalInfo getKrbPrincipalInfo() {
return sp.getKerberosPrincipalInfo();
}
public File getKvConfigFile() {
return kvConfigPath;
}
public String getStoreTrustFile() {
if (sp == null) {
return null;
}
return new File(sp.getConfigDir(), sp.getTruststoreFile()).getPath();
}
/**
* Return the value of the processStartPrefix property.
*/
String getCustomProcessStartupPrefix() {
return customProcessStartupPrefix;
}
boolean verbose() {
return isVerbose;
}
/**
* Advisory interface indicating whether the RepNode is running. This is
* good for testing but should not be trusted 100%.
*/
boolean isRunning(RepNodeId rnid) {
ServiceManager mgr = repNodeServices.get(rnid.getFullName());
if (mgr != null) {
return mgr.isRunning();
}
return false;
}
/*
* Useful for testing
*/
ServiceManager getServiceManager(RepNodeId rnid) {
return repNodeServices.get(rnid.getFullName());
}
ServiceManager getAdminServiceManager() {
return adminService;
}
public static boolean checkLoopback(String host) {
InetSocketAddress isa = new InetSocketAddress(host, 0);
return isa.getAddress().isLoopbackAddress();
}
/**
* Start all components that are configured.
*/
private void startComponents() {
/**
* Admins are arbitrarily started first. A future feature
* having Admins push metadata to the SN may benefit from
* Admins being started first.
* Start the Admin if this SN is hosting it. It may already be running.
* This will be the case during registration of the SNA that is hosting
* the Admin.
*/
if (adminService == null) {
AdminParams ap = ConfigUtils.getAdminParams(kvConfigPath, logger);
if (ap != null && !ap.isDisabled()) {
startAdminInternal(ap, bp.isHostingAdmin());
}
}
List repNodes =
ConfigUtils.getRepNodes(kvConfigPath, logger);
/**
* Start RepNodes.
*/
/*
* Log exceptions from starting a single RepNode and continue
* to start the next RepNode. The administrator will have to do
* 'verify-configuration' to obtain details on the failures
*/
for (ParameterMap map : repNodes) {
RepNodeParams rn = new RepNodeParams(map);
if (!rn.isDisabled()) {
try {
startRepNodeInternal(rn);
} catch (RuntimeException re) {
logsevere((rn.getRepNodeId().getFullName() +
": Unable to start a RepNode on " +
rn.getStorageNodeId().getFullName()),
re);
}
} else {
logger.info(rn.getRepNodeId().getFullName() +
": Skipping automatic start of stopped RepNode ");
}
}
List arbNodes =
ConfigUtils.getArbNodes(kvConfigPath, logger);
for (ParameterMap map : arbNodes) {
ArbNodeParams an = new ArbNodeParams(map);
if (!an.isDisabled()) {
startArbNodeInternal(an);
} else {
logger.info(an.getArbNodeId().getFullName() +
": Skipping automatic start of stopped ArbNode ");
}
}
}
private boolean stopAdminService(boolean stopService, boolean force) {
if (adminService == null) {
return false;
}
boolean stopped = false;
String serviceName = adminService.getService().getServiceName();
try {
logger.info(serviceName + ": Stopping AdminService");
/**
* Make sure the service won't automatically restart.
*/
adminService.dontRestart();
/**
* Try clean shutdown first. If that fails for any reason use a
* bigger hammer to be sure the service is gone. Give the admin
* service a few seconds to come up if it's not already. Stopping
* the service while it's not yet up can be problematic.
*/
if (stopService && !adminService.forceOK(force)) {
CommandServiceAPI admin = ServiceUtils.waitForAdmin
(getHostname(), getRegistryPort(), getLoginManager(),
5, ServiceStatus.RUNNING);
admin.stop(force);
adminService.waitFor(serviceWaitMillis);
stopped = true;
}
} catch (Exception e) {
/**
* Eat the exception but log the problem and make sure that the
* service is really stopped.
*/
logwarning("Exception stopping Admin service", e);
}
if (!stopped) {
adminService.stop();
}
logger.info(serviceName + ": Stopped AdminService");
unbindService(GlobalParams.COMMAND_SERVICE_NAME);
unbindService(GlobalParams.ADMIN_LOGIN_SERVICE_NAME);
mgmtAgent.removeAdmin();
adminService = null;
return true;
}
/**
* Stops all running RN ServiceManagers and optionally rep node services in
* parallel. It does this by creating an executor service and shutting down
* each RN in a thread associated with the service.
*
* @param stopService if true stop the service as well as the manager.
* @param force if true, stop the service by killing the process/thread.
* Note that if force is true, the service is stopped even if stopServices
* is false.
*/
private void stopRepNodeServices(boolean stopService, boolean force) {
if (repNodeServices.isEmpty()) {
return;
}
/*
* Check if any of the RNs managed by the SNA is a master for its
* replication group. If an RN is a master, the SNA should cause a
* master-transfer before shutting down the RN.
*/
masterBalanceManager.transferMastersForShutdown();
final ExecutorService pool =
Executors.newFixedThreadPool(repNodeServices.size(),
new KVThreadFactory("RnShutDownThread",
logger));
for (ServiceManager mgr : repNodeServices.values()) {
pool.execute(new RNShutdownThread(this, mgr, serviceWaitMillis,
stopService, force));
}
pool.shutdown();
/*
* Overhead to provide the thread an opportunity to implement the
* timeout and minimize the chance of a race.
*/
final long overheadMs = 10000;
try {
pool.awaitTermination(serviceWaitMillis + overheadMs,
TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
logger.warning("stopRepNodeservices: Unexpected interrupt" );
return;
}
final List residual = pool.shutdownNow();
for (Runnable t : residual) {
/* Kill the processes, if the RN did not terminate gracefully. */
((RNShutdownThread)t).getMgr().stop();
}
repNodeServices.clear();
}
/**
* Stops all running ARB ServiceManagers and optionally arb node services in
* parallel. It does this by creating an executor service and shutting down
* each AN in a thread associated with the service.
*
* @param stopService if true stop the service as well as the manager.
* @param force if true, stop the service by killing the process/thread.
* Note that if force is true, the service is stopped even if stopServices
* is false.
*/
private void stopArbNodeServices(boolean stopService, boolean force) {
if (arbNodeServices.isEmpty()) {
return;
}
final ExecutorService pool =
Executors.newFixedThreadPool(arbNodeServices.size(),
new KVThreadFactory("AnShutDownThread",
logger));
for (ServiceManager mgr : arbNodeServices.values()) {
pool.execute(new RNShutdownThread(this, mgr, serviceWaitMillis,
stopService, force));
}
pool.shutdown();
/*
* Overhead to provide the thread an opportunity to implement the
* timeout and minimize the chance of a race.
*/
final long overheadMs = 10000;
try {
pool.awaitTermination(serviceWaitMillis + overheadMs,
TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
logger.warning("stopArbNodeservices: Unexpected interrupt" );
return;
}
final List residual = pool.shutdownNow();
for (Runnable t : residual) {
/* Kill the processes, if the AN did not terminate gracefully. */
((RNShutdownThread)t).getMgr().stop();
}
arbNodeServices.clear();
}
/**
* Unbind a managed service from this SNA's registry.
*
* This is done to ensure that even on forcible stop the service is
* cleaned out of the registry. Errors are expected (already unbound)
* and ignored.
*/
void unbindService(String serviceName) {
try {
registry.unbind(serviceName);
} catch (NotBoundException | RemoteException nbe) {
/* ignore */
}
}
/**
* Starts the master balance manager component
*
* @param enabled if true master balancing is enabled. If false it's
* disabled; the SNA will not initiate any rebalancing at this node and
* will decline to participate in rebalancing requests from other SNAs.
*/
private void startMasterBalanceManager(boolean enabled) {
/* Cleanup any existing MBM */
stopMasterBalanceManager();
/* Now start up the master balance manager. */
final SNInfo snInfo =
new MasterBalanceManager.SNInfo(getStoreName(), snid,
getHostname(),
getRegistryPort());
masterBalanceManager =
MasterBalanceManager.create(enabled, snInfo, logger,
getLoginManager());
}
/**
* Stops the master balance manager component
*/
private void stopMasterBalanceManager() {
if (masterBalanceManager == null) {
return;
}
masterBalanceManager.shutdown();
/*
* Leave the iv non-null in the shutdown state, so it can provide
* appropriate responses to MBM requests and so we don't need null
* check guards.
*/
}
/**
* Detect and kill running processes for this store.
*/
private void cleanupRunningComponents() {
/**
* Kill RepNodes
*/
List repNodes =
ConfigUtils.getRepNodes(kvConfigPath, logger);
for (ParameterMap map : repNodes) {
RepNodeParams rn = new RepNodeParams(map);
ManagedService.killManagedProcesses
(getStoreName(), rn.getRepNodeId().getFullName(), getLogger());
}
/**
* Admin may be named as the bootstrap admin or via the config
* file. Try both. Don't kill a currently-managed bootstrap admin, as
* indicated by a non-null adminService.
*/
if (adminService == null) {
ManagedService.killManagedProcesses
(getStoreName(), makeBootstrapAdminName(), getLogger());
}
AdminParams ap = ConfigUtils.getAdminParams(kvConfigPath, logger);
if (ap != null) {
ManagedService.killManagedProcesses
(getStoreName(), ap.getAdminId().getFullName(), getLogger());
}
}
/**
* Make sure that the storage directory, if specified, exists and is a
* directory.
* TODO: should symlinks be created in the SNs directory to point to the
* RepNode directories?
*/
private File validateRepNodeDirectory(RepNodeParams rnp) {
final File file = rnp.getStorageDirectoryFile();
return validateDirectory(file,
rnp.getRepNodeId(),
rnp.getStorageDirectorySize());
}
private File validateArbNodeDirectory(ArbNodeParams arp) {
final File mp =
FileNames.getEnvDir(kvRoot.getAbsolutePath(),
getStoreName(),
null,
snid,
arp.getArbNodeId());
if (mp.exists()) {
validateDirectory(mp, arp.getArbNodeId(), 0L);
}
return mp;
}
private File validateDirectory(File file,
ResourceId id,
long requiredSize) {
if (file == null) {
return null;
}
final String reason = FileUtils.verifyDirectory(file, requiredSize);
if (reason != null) {
final String msg = "Directory specified for "+ id.getFullName() +
" " + reason;
logger.info(msg);
throw new IllegalArgumentException(msg);
}
return file;
}
/**
* Internal method to start a RepNode, shared by external and internal
* callers.
*
* @return true if the rep node was successfully started, or false if an
* exception that is not a RuntimeException is raised
* @throws RuntimeException if a problem occurred validating the rep node
* directory
*/
private boolean startRepNodeInternal(RepNodeParams rnp) {
RepNodeId rnid = rnp.getRepNodeId();
String serviceName = rnid.getFullName();
logger.info(serviceName + ": Starting RepNode");
File repNodeDir;
try {
repNodeDir = validateRepNodeDirectory(rnp);
} catch (RuntimeException re) {
/* log the exception and throw it so that it reaches the caller */
logsevere(serviceName + ": Runtime Exception starting RepNode",
re);
throw re;
}
try {
/**
* Create a ManagedService object used by the ServiceManager to
* start the service.
*/
ManagedRepNode ms = new ManagedRepNode
(sp, rnp, kvRoot, snRoot, getStoreName());
ServiceManager mgr = repNodeServices.get(serviceName);
if (mgr != null) {
/* The service may be running */
boolean isRunning = mgr.isRunning();
if (isRunning) {
logger.info(serviceName +
": Attempt to start a running RepNode.");
return true;
}
logger.info(serviceName + " exists but is not runnable." +
" Attempt to stop it and restart.");
stopRepNode(rnid, true);
/*
* recurse back to startRepNode to make sure state gets set
* correctly.
*/
return startRepNode(rnid);
}
if (useThreads) {
/* start in thread */
mgr = new ThreadServiceManager(this, ms);
} else {
/* start in process */
mgr = new ProcessServiceManager(this, ms);
}
checkForRecovery(rnid, repNodeDir);
mgmtAgent.addRepNode(rnp, mgr);
mgr.start();
/**
* Add service to map of running services.
*/
repNodeServices.put(serviceName, mgr);
logger.info(serviceName + ": Started RepNode");
} catch (Exception e) {
logsevere((serviceName + ": Exception starting RepNode"), e);
return false;
}
return true;
}
/**
* Internal method to start an ArbNode, shared by external and internal
* callers.
*/
private boolean startArbNodeInternal(ArbNodeParams anp) {
ArbNodeId arbid = anp.getArbNodeId();
String serviceName = arbid.getFullName();
logger.info(serviceName + ": Starting ArbNode");
try {
/**
* Create a ManagedService object used by the ServiceManager to
* start the service.
*/
ManagedArbNode ms = new ManagedArbNode
(sp, anp, kvRoot, snRoot, getStoreName());
File arbNodeDir = validateArbNodeDirectory(anp);
ServiceManager mgr = arbNodeServices.get(serviceName);
if (mgr != null) {
/* The service may be running */
boolean isRunning = mgr.isRunning();
if (isRunning) {
logger.info(serviceName +
": Attempt to start a running ArbNode.");
return true;
}
logger.info(serviceName + " exists but is not runnable." +
" Attempt to stop it and restart.");
stopArbNode(arbid, true);
/*
* recurse back to startArbNode to make sure state gets set
* correctly.
*/
return startArbNode(arbid);
}
if (useThreads) {
/* start in thread */
mgr = new ThreadServiceManager(this, ms);
} else {
/* start in process */
mgr = new ProcessServiceManager(this, ms);
}
checkForRecovery(arbid, arbNodeDir);
mgmtAgent.addArbNode(anp, mgr);
mgr.start();
/**
* Add service to map of running services.
*/
arbNodeServices.put(serviceName, mgr);
logger.info(serviceName + ": Started ArbNode");
} catch (Exception e) {
logsevere((serviceName + ": Exception starting ArbNode"), e);
return false;
}
return true;
}
/**
* Utility method for waiting until a RepNode reaches one of the given
* states. Primarily here for the exception handling.
*/
public RepNodeAdminAPI waitForRepNodeAdmin(RepNodeId rnid,
ServiceStatus[] targets) {
return waitForRepNodeAdmin(rnid, targets, repnodeWaitSecs);
}
private RepNodeAdminAPI waitForRepNodeAdmin(RepNodeId rnid,
ServiceStatus[] targets,
int waitSecs) {
final RepNodeAdminAPI rnai;
try {
rnai = ServiceUtils.waitForRepNodeAdmin
(getStoreName(), getHostname(), getRegistryPort(), rnid, snid,
getLoginManager(), waitSecs, targets);
} catch (Exception e) {
File logDir = FileNames.getLoggingDir(kvRoot, getStoreName());
String logName =
logDir + File.separator + rnid.toString() + "*.log";
String msg = "Failed to attach to RepNodeService for " +
rnid + " after waiting " + waitSecs +
" seconds; see log, " + logName + ", on host " +
getHostname() + " for more information.";
logsevere(msg, e);
/*
* Check if the process didn't actually start up, and throw an
* exception if so. That's different from a timeout exception, and
* it would be better to propagate that information.
*/
RegistryUtils.checkForStartupProblem(getStoreName(), getHostname(),
getRegistryPort(), rnid, snid,
getLoginManager());
return null;
}
return rnai;
}
/**
* Utility method for waiting until the Admin reaches the given state.
*/
public CommandServiceAPI waitForAdmin(ServiceStatus target,
int timeoutSecs) {
CommandServiceAPI cs = null;
try {
cs = ServiceUtils.waitForAdmin
(getHostname(), getRegistryPort(), getLoginManager(),
timeoutSecs, target);
} catch (Exception e) {
String msg = "Failed to attach to AdminService for after waiting " +
repnodeWaitSecs + " seconds.";
logger.severe(msg);
throw new IllegalStateException(msg, e);
}
return cs;
}
/**
* Remove the data directory for the resource. This method does not deal
* with storage directories and should not be called for any service that
* is using one.
*/
void removeDataDir(ResourceId rid) {
File dataDir = FileNames.getServiceDir(kvRoot.toString(),
getStoreName(),
null,
snid,
rid);
logger.info("Removing data directory for resource " +
rid + ": " + dataDir);
if (dataDir.exists()) {
removeFiles(dataDir);
}
}
/**
* Stop a running RepNode
*/
public boolean stopRepNode(RepNodeId rnid, boolean force) {
return stopRepNode(rnid, force, serviceWaitMillis);
}
/*
* This function should never fail to stop a RepNode if the RepNode exists
* and is running. The last resort is to forcibly kill the RN, even if the
* force boolean is false.
*/
boolean stopRepNode(RepNodeId rnid, boolean force, int waitMillis) {
boolean stopped = false;
String serviceName = rnid.getFullName();
logger.info(serviceName + ": stopRepNode called");
ServiceManager mgr = repNodeServices.get(serviceName);
boolean isRunning = true;
if (mgr != null) {
isRunning = mgr.isRunning();
}
if (mgr == null || !isRunning) {
logger.info(serviceName + ": RepNode is not running");
}
if (mgr == null) {
return false;
}
/*
* Set the service state in the config file to disabled. Once this is
* done the RN will not be automatically restarted.
*/
setServiceStoppedState
(serviceName, ParameterState.COMMON_DISABLED, true);
try {
/*
* Make sure the service won't automatically restart.
*/
mgr.dontRestart();
/*
* If force is true, skip directly to killing the service.
*/
if (isRunning && !mgr.forceOK(force)) {
ManagedRepNode mrn = (ManagedRepNode) mgr.getService();
RepNodeAdminAPI rna = mrn.getRepNodeAdmin(this);
rna.shutdown(force);
/*
* Wait for the execution context (process or thread).
*/
mgr.waitFor(waitMillis);
stopped = true;
logger.info(serviceName + ": Stopped RepNode");
}
} catch (RuntimeException | RemoteException e) {
logwarning((serviceName + ": Exception stopping RepNode"), e);
} finally {
/*
* Ask the ServiceManager to stop it if shutdown failed.
*/
if (!stopped) {
mgr.stop();
}
/*
* Remove service and set active state in RNP to false.
*/
unbindService(makeRepNodeBindingName(serviceName));
repNodeServices.remove(serviceName);
try {
mgmtAgent.removeRepNode(rnid);
} catch (RuntimeException ce) {
logwarning
((serviceName + ": Exception removing RepNode from mgmt" +
" agent"), ce);
}
}
return isRunning;
}
String makeRepNodeBindingName(String fullName) {
return RegistryUtils.bindingName(getStoreName(),
fullName,
RegistryUtils.InterfaceType.ADMIN);
}
/**
* Stop a running ArbNode
*/
public boolean stopArbNode(ArbNodeId rnid, boolean force) {
return stopArbNode(rnid, force, serviceWaitMillis);
}
/*
* This function should never fail to stop a ArbNode if the ArbNode exists
* and is running. The last resort is to forcibly kill the AN, even if the
* force boolean is false.
*/
boolean stopArbNode(ArbNodeId arbid, boolean force, int waitMillis) {
boolean stopped = false;
String serviceName = arbid.getFullName();
logger.info(serviceName + ": stopArbNode called");
ServiceManager mgr = arbNodeServices.get(serviceName);
boolean isRunning = true;
if (mgr != null) {
isRunning = mgr.isRunning();
}
if (mgr == null || !isRunning) {
logger.info(serviceName + ": ArbNode is not running");
}
if (mgr == null) {
return false;
}
/*
* Set the service state in the config file to disabled. Once this is
* done the AN will not be automatically restarted.
*/
setServiceStoppedState
(serviceName, ParameterState.COMMON_DISABLED, true);
try {
/*
* Make sure the service won't automatically restart.
*/
mgr.dontRestart();
/*
* If force is true, skip directly to killing the service.
*/
if (isRunning && !mgr.forceOK(force)) {
ManagedArbNode man = (ManagedArbNode) mgr.getService();
ArbNodeAdminAPI ana = man.getArbNodeAdmin(this);
ana.shutdown(force);
/*
* Wait for the execution context (process or thread).
*/
mgr.waitFor(waitMillis);
stopped = true;
logger.info(serviceName + ": Stopped ArbNode");
}
} catch (RuntimeException | RemoteException e) {
logwarning((serviceName + ": Exception stopping ArbNode"), e);
} finally {
/*
* Ask the ServiceManager to stop it if shutdown failed.
*/
if (!stopped) {
mgr.stop();
}
/*
* Remove service and set active state in ANP to false.
*/
unbindService(makeRepNodeBindingName(serviceName));
arbNodeServices.remove(serviceName);
try {
mgmtAgent.removeArbNode(arbid);
} catch (RuntimeException ce) {
logwarning
((serviceName + ": Exception removing ArbNode from mgmt" +
" agent"), ce);
}
}
return isRunning;
}
/**
* Modify the configuration to mark all services as disabled, failing if it
* can determine that the SNA or any of its services are currently running.
* Makes no configuration changes if the SNA is not registered.
*
* @throws RuntimeException if fails to disable services
*/
public void disableServices() {
logger = LoggerUtils.getBootstrapLogger
(bootstrapDir, FileNames.BOOTSTRAP_SNA_LOG, snaName);
final File configPath = new File(bootstrapDir, bootstrapFile);
bp = ConfigUtils.getBootstrapParams(configPath, logger);
/* Check for correct security configuration */
boolean securityOK = false;
try {
initSecurity();
securityOK = true;
} catch (RuntimeException e) {
logger.info(e.getMessage());
}
/* Check for a running SNA if the security configuration was correct */
if (securityOK) {
sp.initRMISocketPolicies();
BootstrapParams.initRegistryCSF(sp);
snaSecurity = new SNASecurity(
this, bp, sp, null /* gp */, null /* snp */, logger);
final String serviceName;
if (isRegistered()) {
final StorageNodeId snid1 = new StorageNodeId(bp.getId());
serviceName = RegistryUtils.bindingName(
bp.getStoreName(), snid1.getFullName(),
RegistryUtils.InterfaceType.MAIN);
} else {
serviceName = GlobalParams.SNA_SERVICE_NAME;
}
try {
RegistryUtils.getStorageNodeAgent(
bp.getHostname(), bp.getRegistryPort(), serviceName,
getLoginManager());
throw new IllegalStateException(
"Attempt to disable services when the storage node" +
" agent is running");
} catch (RemoteException | NotBoundException ignore) {
}
}
logger.info("SNA not found");
/* There are no services to disable if the SNA is unregistered */
if (!isRegistered()) {
return;
}
/*
* Check for services that might be running even though the SNA was not
* found
*/
final String rootPath = CommandParser.ROOT_FLAG + " " + bootstrapDir;
final String configFile = CONFIG_FLAG + " " + bootstrapFile;
final List snaProcesses = ManagedService.findManagedProcesses(
"StorageNodeAgentImpl", rootPath, configFile, logger);
final List managedProcesses =
ManagedService.findManagedProcesses(
getStoreName(), rootPath, configFile, logger);
if (!snaProcesses.isEmpty() || !managedProcesses.isEmpty()) {
throw new IllegalStateException(
"Attempt to disable services when the storage node agent or" +
" some services are running");
}
logger.info("Services not found");
/* Initialize more fields in preparation for disabling services */
kvRoot = new File(bp.getRootdir());
initStorePaths();
final StorageNodeParams snp =
ConfigUtils.getStorageNodeParams(kvConfigPath, logger);
final GlobalParams gp =
ConfigUtils.getGlobalParams(kvConfigPath, logger);
logger = LoggerUtils.getLogger(StorageNodeAgentImpl.class, gp, snp);
/* Disable RNs */
for (final ParameterMap map :
ConfigUtils.getRepNodes(kvConfigPath, logger)) {
final RepNodeParams rn = new RepNodeParams(map);
if (!rn.isDisabled()) {
rn.setDisabled(true);
replaceRepNodeParams(rn);
}
}
/* Disable admin */
final AdminParams ap =
ConfigUtils.getAdminParams(kvConfigPath, logger);
if ((ap != null) && !ap.isDisabled()) {
ap.setDisabled(true);
replaceAdminParams(ap.getAdminId(), ap.getMap());
}
logger.info("Disabled all services");
}
private void startBootstrapAdmin() {
if (bp.isHostingAdmin()) {
createBootstrapAdmin = true;
}
if (createBootstrapAdmin) {
startAdminInternal(null, true);
} else {
logger.info
("isHostingAdmin is false; not starting Bootstrap Admin");
}
}
/**
* Used by SNAImpl. It will never pass a null AdminParams.
*/
public boolean startAdmin(AdminParams ap) {
/**
* Set active state in AP. This must be done before the service is
* started to avoid conflict on the file.
*/
setServiceStoppedState
(ap.getAdminId().getFullName(),
ParameterState.COMMON_DISABLED, false);
return startAdminInternal(ap, getBootstrapParams().isHostingAdmin());
}
/**
* Internal method to start an AdminService, shared by external and
* internal callers.
*/
private boolean startAdminInternal(AdminParams ap, boolean isBootstrap) {
if (ap == null && !isBootstrap) {
throw new IllegalStateException
("Params for admin do not exist, should have been created.");
}
String serviceName = (ap != null ?
ap.getAdminId().getFullName() :
ManagedService.BOOTSTRAP_ADMIN_NAME);
try {
/**
* Create a ManagedService object used by the ServiceManager to
* start the service.
*/
if (adminService != null) {
/* The service may be running */
boolean isRunning = adminService.isRunning();
if (isRunning) {
logger.info(serviceName +
": Attempt to start a running AdminService");
return true;
}
logger.info(serviceName + " exists but is not runnable." +
" Attempt to stop it and restart.");
stopAdminService(true, true);
/* fall through to start */
}
ManagedAdmin ms;
logger.info(serviceName + ": Starting AdminService");
if (ap != null) {
ms = new ManagedAdmin
(sp, ap, kvRoot, snRoot, getStoreName());
} else {
ms = new ManagedBootstrapAdmin(this);
}
final ServiceManager mgr;
if (useThreads) {
/* start in thread */
mgr = new ThreadServiceManager(this, ms);
} else {
/* start in process */
mgr = new ProcessServiceManager(this, ms);
}
if (ap != null) {
checkForRecovery(ap.getAdminId(), null);
}
mgmtAgent.addAdmin(ap, mgr);
mgr.start();
adminService = mgr;
logger.info(serviceName + ": Started AdminService");
} catch (Exception e) {
String msg = "Exception starting AdminService: " + e;
logger.severe(msg);
return false;
}
return true;
}
synchronized boolean isRegistered() {
return getStoreName() != null;
}
/**
* Make a service name for the BootstrapAdmin. It must include the
* registry port to uniquely identify the bootstrap admin process in the
* event there is more than one SN on the host.
*/
public String makeBootstrapAdminName() {
return ManagedService.BOOTSTRAP_ADMIN_NAME + "." + getRegistryPort();
}
/**
* Setup all that is needed for logging and monitoring, for registered
* SNAs.
*/
private void setupMonitoring(GlobalParams globalParams,
StorageNodeParams snp) {
if (monitorAgent != null) {
return;
}
/*
* The AgentRepository is the buffered monitor data and belongs
* to the monitorAgent, but is instantiated outside to take care of
* initialization dependencies.
*/
AgentRepository monitorBuffer =
new AgentRepository(globalParams.getKVStoreName(),
snp.getStorageNodeId(),
snp.getMonitorAgentRepoSize());
snParameterTracker.addListener(monitorBuffer.new SNParamsListener());
statusTracker.addListener(monitorBuffer);
monitorAgent = new MonitorAgentImpl(this,
globalParams,
snp,
sp,
monitorBuffer,
statusTracker);
}
/**
* Send Storage Node and bootstrap information back as a reply to the
* register() call
*/
public static class RegisterReturnInfo {
final private List maps;
private ParameterMap bootMap;
private ParameterMap storageDirMap;
private ParameterMap adminDirMap;
private ParameterMap rnLogDirMap;
public RegisterReturnInfo(StorageNodeAgent sna) {
final BootstrapParams bp = sna.getBootstrapParams();
maps = new ArrayList<>();
bootMap = bp.getMap().copy();
bootMap.setParameter(ParameterState.GP_ISLOOPBACK,
Boolean.toString(sna.isLoopbackAddress()));
storageDirMap = bp.getStorageDirMap().copy();
adminDirMap = bp.getAdminDirMap().copy();
rnLogDirMap = bp.getRNLogDirMap().copy();
maps.add(bootMap);
maps.add(storageDirMap);
maps.add(adminDirMap);
maps.add(rnLogDirMap);
}
public RegisterReturnInfo(List maps) {
this.maps = maps;
bootMap = null;
storageDirMap = null;
adminDirMap = null;
rnLogDirMap = null;
for (ParameterMap pmap : maps) {
if (pmap.getName().equals(ParameterState.BOOTSTRAP_PARAMS)) {
bootMap = pmap;
}
if (pmap.getName().equals
(ParameterState.BOOTSTRAP_MOUNT_POINTS)) {
storageDirMap = pmap;
}
if (pmap.getName().equals
(ParameterState.BOOTSTRAP_ADMIN_MOUNT_POINTS)) {
adminDirMap = pmap;
}
if (pmap.getName().equals
(ParameterState.BOOTSTRAP_RNLOG_MOUNT_POINTS)) {
rnLogDirMap = pmap;
}
}
}
public List getMaps() {
return maps;
}
public ParameterMap getBootMap() {
return bootMap;
}
public ParameterMap getStorageDirMap() {
return storageDirMap;
}
public ParameterMap getAdminDirMap() {
return adminDirMap;
}
public ParameterMap getRNLogDirMap() {
return rnLogDirMap;
}
public boolean getIsLoopback() {
if (bootMap == null) {
throw new IllegalStateException
("BootMap cannot be null when asking for loopback info");
}
Parameter p = bootMap.get(ParameterState.GP_ISLOOPBACK);
if (p == null) {
throw new IllegalStateException
("GP_ISLOOPBACK parameter is not set in boot map");
}
return p.asBoolean();
}
}
/**
* Implementation methods for StorageNodeAgentInterface. These are
* called by the public interface methods that wrap the calls with a
* ProcessFaultHandler object for consistent exception handling.
*/
List register(GlobalParams gp,
StorageNodeParams snp,
boolean hostingAdmin) {
logger.info("Register: root: " + kvRoot + ", store: " +
gp.getKVStoreName() + ", hostingAdmin: " + hostingAdmin);
/**
* Allow retries to succeed.
*/
if (isRegistered()) {
logger.info("Register: Storage Node Agent is already registered " +
"to " + getStoreName());
return new RegisterReturnInfo(this).getMaps();
}
if (gp.isLoopbackSet()) {
if (gp.isLoopback() != isLoopbackAddress()) {
String msg="Register: Cannot mix loopback and non-loopback " +
"addresses in the same store. The store value " +
(gp.isLoopback() ? "is" : "is not") +
" configured to use loopback addresses but storage node " +
snp.getHostname() + ":" + snp.getRegistryPort() + " " +
(isLoopbackAddress() ? "is" : "is not") +
" a loopback address.";
logger.info(msg);
throw new IllegalStateException(msg);
}
} else {
gp.setIsLoopback(isLoopbackAddress());
}
/*
* Make sure that system information, such as number of CPUs, is set if
* it is available. It is returned in RegisterReturnInfo and set in
* snp.setInstallationInfo().
*/
setSystemInfo();
/**
* Create the ultimate return object and initialize fields in the
* StorageNodeParams that the SNA owns.
*/
RegisterReturnInfo rri = new RegisterReturnInfo(this);
snp.setInstallationInfo
(rri.getBootMap(), rri.getStorageDirMap(), rri.getAdminDirMap(),
rri.getRNLogDirMap(), hostingAdmin);
/**
* Initialize state from parameters.
*/
initSNParams(snp);
try {
/**
* There is a test-only race where the bootstrap admin may still be
* coming up so make sure it is running before continuing.
*/
if (adminService != null) {
ServiceUtils.waitForAdmin(getHostname(), getRegistryPort(),
getLoginManager(), 40,
ServiceStatus.RUNNING);
}
/**
* If not hosting the admin, shut down the bootstrap admin.
*/
if (!hostingAdmin) {
stopAdminService(true, true);
}
/**
* Rewrite the bootstrap config file with the KV Store name and Id
* for this SN. Do this after testing kvConfigPath above to make
* reverting to bootstrap state automatic.
*/
File configPath = new File(bootstrapDir, bootstrapFile);
bp.setStoreName(gp.getKVStoreName());
bp.setId(snp.getStorageNodeId().getStorageNodeId());
bp.setHostingAdmin(hostingAdmin);
ConfigUtils.createBootstrapConfig(bp, configPath, logger);
/**
* Make sure the kvstore directory has been created. This call
* uses state set in the BootstrapParams above.
*/
ensureStoreDirectory();
/**
* If there is a configuration file already this is bad. It means
* the SNA was not registered but there is state that appears
* otherwise. kvConfigPath is set in ensureStoreDirectory().
*/
if (kvConfigPath.exists()) {
String msg = "Configuration file was not expected in store " +
"directory: " + kvConfigPath;
throw new IllegalStateException(msg);
}
/**
* Change Logger instances now that this class has an identity.
*/
String newName = snp.getStorageNodeId().getFullName();
logger = LoggerUtils.getLogger(StorageNodeAgentImpl.class, gp, snp);
logger.log(Level.FINE,
"Storage Node named {0} Registering to store {1}",
new Object[]{newName, getStoreName()});
snai.getFaultHandler().setLogger(logger);
if (adminService != null) {
adminService.resetLogger(logger);
}
/**
* Create a new KVstore config file for the store.
*/
LoadParameters lp = new LoadParameters();
lp.addMap(snp.getMap());
if (snp.getStorageDirMap() != null) {
lp.addMap(snp.getStorageDirMap());
}
if (snp.getAdminDirMap() != null) {
lp.addMap(snp.getAdminDirMap());
}
if (snp.getRNLogDirMap() != null) {
lp.addMap(snp.getRNLogDirMap());
}
lp.addMap(gp.getMap());
lp.saveParameters(kvConfigPath, logger);
/**
* Restart as if coming up for the first time but registered this
* time.
*/
start();
} catch (Exception e) {
/**
* Any exceptions from register() will revert the SNA to bootstrap
* mode.
*/
String msg = "Register failed: " + e.getMessage() + "\n" +
LoggerUtils.getStackTrace(e);
revertToBootstrap();
if (adminService == null) {
startBootstrapAdmin();
}
logger.severe(msg);
throw new IllegalStateException(msg, e);
}
return rri.getMaps();
}
public void shutdown(boolean stopServices, boolean force) {
logger.info(snaName + ": Shutdown starting, " +
(stopServices ? "stopping services" :
"not stopping services") +
(force ? " (forced)" : ""));
statusTracker.update(ServiceStatus.STOPPING);
/**
* Stop running services. Minimally cause the SNA to not attempt to
* restart.
*/
stopRepNodeServices(stopServices, force);
stopMasterBalanceManager();
stopArbNodeServices(stopServices, force);
assert TestHookExecute.doHookIfSet(mbmPostShutdownHook, this);
stopAdminService(stopServices, force);
cleanupRegistry();
logger.info(snaName + ": Shutdown complete");
statusTracker.update(ServiceStatus.STOPPED);
if (collectorService != null) {
collectorService.shutdown();
}
mgmtAgent.shutdown();
}
boolean createAdmin(AdminParams adminParams) {
checkRegistered("createAdmin");
String adminName = adminParams.getAdminId().getFullName();
logger.info(adminName + ": Creating AdminService");
/**
* Creation of RepNodes, admins and parameter changes are synchronized
* in order to coordinate changes to the config file(s).
*/
synchronized(this) {
/**
* Add the new Admin to configuration.
*/
LoadParameters lp =
LoadParameters.getParameters(kvConfigPath, logger);
/**
* Does the Admin already exist?
*/
if (lp.getMap(adminName) != null) {
String msg = adminName +
": AdminService exists in config file";
logger.info(msg);
} else {
lp.addMap(adminParams.getMap());
lp.saveParameters(kvConfigPath, logger);
}
assert TestHookExecute.doHookIfSet(restartAdminHook, this);
/**
* If this SN is hosting the admin, don't start it; it is already
* running. It is possible that "isHostingAdmin()" will be true
* and there is no bootstrap admin running. This may be a
* test-only case, but handle it anyway.
*/
if (bp.isHostingAdmin() && adminService != null) {
ManagedAdmin ma = (ManagedAdmin) adminService.getService();
if (ma instanceof ManagedBootstrapAdmin) {
((ManagedBootstrapAdmin) ma).resetAsManagedAdmin(
adminParams, kvRoot, securityDir, snRoot,
getStoreName(), logger);
}
/*
* Give the admin a chance to read potentially modified
* parameters
*/
try {
ma.getAdmin(this).newParameters();
} catch (RemoteException e) {
String msg = adminName +
": Failed to contact bootstrap AdminService";
logger.info(msg);
throw new IllegalStateException(msg, e);
}
logger.info(adminName + ": Created AdminService");
return true;
}
return startAdminInternal(adminParams, false);
}
}
/**
* TODO: eventually use adminId if multiple admins are supported.
*/
public boolean stopAdmin(@SuppressWarnings("unused") AdminId adminId,
boolean force) {
if (adminService == null) {
String msg = "Stopping AdminService: service is not running";
/**
* Throw an exception if the service has not been created.
*/
if (ConfigUtils.getAdminParams(kvConfigPath, logger) == null) {
msg += "; service does not exist";
throw new IllegalStateException(msg);
}
logger.warning(msg);
return false;
}
String adminName = adminService.getService().getServiceName();
boolean retval = stopAdminService(true, force);
/**
* Set disabled to true in config file
*/
setServiceStoppedState
(adminName, ParameterState.COMMON_DISABLED, true);
return retval;
}
/**
* Idempotent -- if the service does not exist, it is fine.
* NOTE: for now the adminId is ignored as the SNA can only support a
* single admin instance. In order to fully support multiple admins
* the name of the ParameterMap stored in the SNA's config file must
* change to use the admin ID vs "adminParams" in order to uniquely
* identify the correct instance to remove. This is an upgrade issue.
*/
boolean destroyAdmin(AdminId adminId, boolean deleteData) {
boolean retval = false;
if (adminService == null) {
logger.warning("Destroying AdminService: service is not running");
} else {
String serviceName = adminService.getService().getServiceName();
logger.info(serviceName + ": Destroying AdminService");
retval = stopAdminService(true, true);
}
/**
* Set bootstrap configuration to indicate that the Admin is no longer
* hosted, if it was previously.
*/
if (bp.isHostingAdmin()) {
bp.setHostingAdmin(false);
File configPath = new File(bootstrapDir, bootstrapFile);
ConfigUtils.createBootstrapConfig(bp, configPath, logger);
}
/**
* Remove admin from the config file. This happens even if the
* stopAdminService() call above failed.
*/
removeConfigurable(adminId, ParameterState.ADMIN_TYPE, deleteData);
logger.info("Destroyed AdminService");
return retval;
}
/**
* Remove a RepNode or Admin from the config file.
*/
boolean removeConfigurable(ResourceId rid, String type,
boolean deleteData) {
ParameterMap map =
ConfigUtils.removeComponent(kvConfigPath, rid, type, logger);
if (deleteData && map != null) {
/**
* Determine data dir. If this is not a RepNode it will not
* have a storage directory and the data dir will be in the default
* location.
*/
if (map.getType().equals(ParameterState.REPNODE_TYPE)) {
RepNodeParams rnp = new RepNodeParams(map);
File file = rnp.getStorageDirectoryFile();
if (file != null) {
File rnDir = new File(file, rid.getFullName());
if (rnDir.exists()) {
logger.info("Removing data directory for RepNode " +
rid + ": " + rnDir);
removeFiles(rnDir);
}
return true;
}
}
/**
* If here, remove the data dir in the default location. It may be
* either a RepNode or Admin.
*/
removeDataDir(rid);
}
return (map != null);
}
StringBuilder getStartupBuffer(ResourceId rid) {
if (rid instanceof RepNodeId) {
ServiceManager mgr = repNodeServices.get(rid.getFullName());
if (mgr != null) {
return mgr.getService().getStartupBuffer();
}
} else {
ManagedAdmin ma = (ManagedAdmin) adminService.getService();
if (rid.equals(ma.getResourceId())) {
return ma.getStartupBuffer();
}
}
throw new IllegalStateException
("Resource " + rid + " is not running on this storage node");
}
/**
* Utility method used only by createRepNode to configure a newly-created
* RepNode. This method will never fail. If the call to configure fails
* it is logged (as SEVERE) but the RepNode will still exist as far as the
* SNA is concerned. If the RepNode does not manage to acquire a topology
* that is a problem for the administrator.
*/
private void
configureRepNode(Set> metadataSet,
RepNodeId rnid) {
/**
* When creating the RepNode it needs to be configured using the
* Topology. Don't wait all that long. If the service is going
* to start up and it's taking a while it will eventually get the
* configuration information from other sources.
*/
ServiceStatus[] targets =
{ServiceStatus.WAITING_FOR_DEPLOY, ServiceStatus.RUNNING};
/* This will log a failure to get the interface */
RepNodeAdminAPI rnai = waitForRepNodeAdmin(rnid, targets,
repnodeWaitSecs);
if (rnai != null) {
try {
rnai.configure(metadataSet);
} catch (RemoteException re) {
logsevere((rnid + ": Failed to configure RepNodeService"), re);
}
}
}
private void setServiceStoppedState(String serviceName,
String paramName,
boolean state) {
LoadParameters lp =
LoadParameters.getParameters(kvConfigPath, logger);
ParameterMap map = lp.getMap(serviceName);
if (map != null) {
map.setParameter(paramName, Boolean.toString(state));
lp.saveParameters(kvConfigPath, logger);
}
}
RepNodeParams lookupRepNode(RepNodeId rnid) {
return ConfigUtils.getRepNodeParams(kvConfigPath, rnid, logger);
}
boolean createRepNode(RepNodeParams repNodeParams,
Set> metadataSet) {
/**
* Creation of RepNodes, admins and parameter changes are synchronized
* in order to coordinate changes to the config file(s).
*/
synchronized(this) {
RepNodeId rnid = repNodeParams.getRepNodeId();
String serviceName = rnid.getFullName();
logger.info(serviceName + ": Creating RepNode");
/**
* Add the new RepNode to configuration.
*/
LoadParameters lp =
LoadParameters.getParameters(kvConfigPath, logger);
/**
* The RepNode may be in the config file but not configured. This
* would happen if the SNA exited after writing the config file but
* before the RepNode configure() method succeeded.
*
* So, allow the RepNode to exist and be running and if it's not
* yet deployed, deploy (configure) it. If it is deployed then
* this call was in error so return false.
*/
boolean startService = true;
RepNodeParams rnp = null;
ParameterMap map = lp.getMap(serviceName);
if (map != null) {
ServiceManager mgr = repNodeServices.get(serviceName);
rnp = new RepNodeParams(map);
if (mgr != null) {
/**
The service may have been created. Don't start it.
*/
String msg = serviceName +
": RepNode exists, not starting process";
logger.info(msg);
startService = false;
} else {
String msg = serviceName +
": RepNode exists but is not running, will attempt " +
"to start it";
logger.info(msg);
}
} else {
lp.addMap(repNodeParams.getMap());
lp.saveParameters(kvConfigPath, logger);
rnp = ConfigUtils.getRepNodeParams(kvConfigPath, rnid, logger);
}
/*
* Test: exit here between creation and startup.
*/
assert TestHookExecute.doHookIfSet(restartRNHook, this);
assert TestHookExecute.doHookIfSet(FAULT_HOOK, 0);
/*
* Start the service.
*/
if (startService) {
if (!startRepNodeInternal(rnp)) {
return false;
}
}
/*
* Test exit after start, before configure.
*/
assert TestHookExecute.doHookIfSet(stopRNHook, this);
logger.info(serviceName + ": Configuring RepNode");
configureRepNode(metadataSet, rnid);
logger.info(serviceName + ": Created RepNode");
return true;
}
}
/**
* Start an already created RepNode. Public to support testing.
*/
public boolean startRepNode(RepNodeId repNodeId) {
String serviceName = repNodeId.getFullName();
RepNodeParams rnp =
ConfigUtils.getRepNodeParams(kvConfigPath, repNodeId, logger);
if (rnp == null) {
String msg = serviceName + ": RepNode has not been created";
logger.info(msg);
/* This rep node should have been created by this point. */
throw new IllegalStateException(msg);
}
/**
* Set active state in RNP.
*/
setServiceStoppedState
(serviceName, ParameterState.COMMON_DISABLED, false);
return startRepNodeInternal(rnp);
}
/**
* Utility method for waiting until a ArbNode reaches one of the given
* states. Primarily here for the exception handling.
*/
public ArbNodeAdminAPI waitForArbNodeAdmin(ArbNodeId anid,
ServiceStatus[] targets) {
return waitForArbNodeAdmin(anid, targets, repnodeWaitSecs);
}
private ArbNodeAdminAPI waitForArbNodeAdmin(ArbNodeId anid,
ServiceStatus[] targets,
int waitSecs) {
ArbNodeAdminAPI anai = null;
try {
anai = ServiceUtils.waitForArbNodeAdmin
(getStoreName(), getHostname(), getRegistryPort(), anid, snid,
getLoginManager(), waitSecs, targets);
} catch (Exception e) {
File logDir = FileNames.getLoggingDir(kvRoot, getStoreName());
String logName =
logDir + File.separator + anid.toString() + "*.log";
String msg = "Failed to attach to ArbNodeService for " +
anid + " after waiting " + waitSecs +
" seconds; see log, " + logName + ", on host " +
getHostname() + " for more information.";
logsevere(msg, e);
/*
* Check if the process didn't actually start up, and throw an
* exception if so. That's different from a timeout exception, and
* it would be better to propagate that information.
*/
RegistryUtils.checkForStartupProblem(getStoreName(), getHostname(),
getRegistryPort(), anid, snid,
getLoginManager());
return null;
}
return anai;
}
ArbNodeParams lookupArbNode(ArbNodeId arid) {
return ConfigUtils.getArbNodeParams(kvConfigPath, arid, logger);
}
boolean createArbNode(ArbNodeParams arbNodeParams) {
/**
* Creation of ArbNodes, admins and parameter changes are synchronized
* in order to coordinate changes to the config file(s).
*/
synchronized(this) {
ArbNodeId anid = arbNodeParams.getArbNodeId();
String serviceName = anid.getFullName();
logger.info(serviceName + ": Creating ArbNode");
/**
* Add the new ArbNode to configuration.
*/
LoadParameters lp =
LoadParameters.getParameters(kvConfigPath, logger);
/**
* The ArbNode may be in the config file but not configured. This
* would happen if the SNA exited after writing the config file but
* before the ArbNode configure() method succeeded.
*
* So, allow the ArbNode to exist and be running and if it's not
* yet deployed, deploy (configure) it. If it is deployed then
* this call was in error so return false.
*/
boolean startService = true;
ArbNodeParams anp = null;
ParameterMap map = lp.getMap(serviceName);
if (map != null) {
ServiceManager mgr = arbNodeServices.get(serviceName);
anp = new ArbNodeParams(map);
if (mgr != null) {
/**
The service may have been created. Don't start it.
*/
String msg = serviceName +
": ArbNode exists, not starting process";
logger.info(msg);
startService = false;
} else {
String msg = serviceName +
": ArbNode exists but is not running, will attempt " +
"to start it";
logger.info(msg);
}
} else {
lp.addMap(arbNodeParams.getMap());
lp.saveParameters(kvConfigPath, logger);
anp = ConfigUtils.getArbNodeParams(kvConfigPath, anid, logger);
}
/*
* Test: exit here between creation and startup.
*/
assert TestHookExecute.doHookIfSet(restartRNHook, this);
assert TestHookExecute.doHookIfSet(FAULT_HOOK, 0);
/*
* Start the service.
*/
if (startService) {
if (!startArbNodeInternal(anp)) {
return false;
}
}
/*
* Test exit after start, before waiting
*/
assert TestHookExecute.doHookIfSet(stopRNHook, this);
ServiceStatus[] targets =
{ServiceStatus.WAITING_FOR_DEPLOY, ServiceStatus.RUNNING};
waitForArbNodeAdmin(anid, targets, repnodeWaitSecs);
logger.info(serviceName + ": Created ArbNode");
return true;
}
}
/**
* Start an already created ArbNode.
*/
public boolean startArbNode(ArbNodeId arbNodeId) {
String serviceName = arbNodeId.getFullName();
ArbNodeParams arp =
ConfigUtils.getArbNodeParams(kvConfigPath, arbNodeId, logger);
if (arp == null) {
String msg = serviceName + ": ArbNode has not been created";
logger.info(msg);
/* This arb node should have been created by this point. */
throw new IllegalStateException(msg);
}
/**
* Set active state in arp.
*/
setServiceStoppedState
(serviceName, ParameterState.COMMON_DISABLED, false);
return startArbNodeInternal(arp);
}
/**
* Test-oriented interface to wait for the RepNode to exit.
*/
protected boolean waitForRepNodeExit(RepNodeId rnid, int timeoutSecs) {
String serviceName = rnid.getFullName();
ServiceManager mgr = repNodeServices.get(serviceName);
if (mgr != null) {
mgr.waitFor(timeoutSecs * 1000);
return true;
}
return false;
}
/**
* Checks the specified parameters for correctness. Throws an
* IllegalArgumentException if a parameter is found to be invalid. If
* id is non-null then the parameters are for that service. Otherwise
* the global parameters are checked.
*
* @param params parameter map to check
* @param id the service associated with the parameters or null
*
* @throws IllegalArgumentException if an invalid parameter is found
*/
void checkParams(ParameterMap params, ResourceId id) {
if (id == null) {
checkGlobalParams(params,
ConfigUtils.getStorageNodeParams(
kvConfigPath).getMap());
} else if (id instanceof StorageNodeId) {
checkSNParams(params,
ConfigUtils.getGlobalParams(kvConfigPath).getMap());
}
}
/**
* Checks Global components parameters. Throws an IllegalArgumentException
* if a parameter is found to be invalid.
*/
public static void checkGlobalParams(ParameterMap params,
ParameterMap snParams) {
final List errors = new ArrayList<>();
/*
* Loop through each parameter. If a problem is found set reason to
* a non-null value which will be used in the IllegalArgumentException
* message.
*/
for (Parameter p : params) {
String reason = null;
switch (p.getName()) {
case ParameterState.GP_COLLECTOR_ENABLED:
final boolean collectorEnabled = params.getOrDefault(
ParameterState.GP_COLLECTOR_ENABLED).asBoolean();
final String mgmtClass =
snParams.getOrDefault(COMMON_MGMT_CLASS).asString();
reason = checkCollectorViolations(mgmtClass, collectorEnabled);
break;
}
if (reason != null) {
errors.add(p.getName() + ": " + reason);
}
}
if (!errors.isEmpty()) {
final StringBuilder sb = new StringBuilder();
if (errors.size() == 1) {
sb.append("Invalid parameter: ");
sb.append(errors.get(0));
} else {
sb.append("Invalid parameters:");
for (String error : errors) {
sb.append("\n\t").append(error);
}
}
throw new IllegalArgumentException(sb.toString());
}
}
/**
* Checks SN parameters. Throws an IllegalArgumentException if a parameter
* is found to be invalid.
*/
public static void checkSNParams(ParameterMap params,
ParameterMap gParams) {
assert params != null;
assert gParams != null;
final List errors = new ArrayList<>();
/* Special case storage directories */
if (isStorageDirMap(params)) {
for (Parameter p : params) {
final String reason =
FileUtils.verifyDirectory(new File(p.getName()),
SizeParameter.getSize(p));
if (reason != null) {
errors.add(reason);
}
}
if (!errors.isEmpty()) {
final StringBuilder sb = new StringBuilder();
if (errors.size() == 1) {
sb.append("Invalid storage directory: ");
sb.append(errors.get(0));
} else {
sb.append("Invalid storage directories:");
for (String error : errors) {
sb.append("\n\tdirectory: ").append(error);
}
}
throw new IllegalArgumentException(sb.toString());
}
return;
}
/*
* Loop through each parameter. If a problem is found set reason to
* a non-null value which will be used in the IllegalArgumentException
* message.
*/
for (Parameter p : params) {
String reason = null;
switch (p.getName()) {
case ParameterState.SN_ROOT_DIR_PATH:
reason = FileUtils.verifyDirectory(new File(p.asString()));
break;
case ParameterState.SN_ROOT_DIR_SIZE:
final String rootDir =
params.getOrDefault(ParameterState.SN_ROOT_DIR_PATH).asString();
reason = FileUtils.verifyDirectory(new File(rootDir),
SizeParameter.getSize(p));
break;
case ParameterState.COMMON_MGMT_CLASS:
final boolean collectorEnabled =
gParams.getOrDefault(
ParameterState.GP_COLLECTOR_ENABLED).asBoolean();
final String mgmtClass = params.getOrDefault(
ParameterState.COMMON_MGMT_CLASS).asString();
reason = checkCollectorViolations(mgmtClass, collectorEnabled);
}
if (reason != null) {
errors.add(p.getName() + ": " + reason);
}
}
if (!errors.isEmpty()) {
final StringBuilder sb = new StringBuilder();
if (errors.size() == 1) {
sb.append("Invalid parameter: ");
sb.append(errors.get(0));
} else {
sb.append("Invalid parameters:");
for (String error : errors) {
sb.append("\n\t").append(error);
}
}
throw new IllegalArgumentException(sb.toString());
}
}
public static boolean isFileSystemCheckRequired(ParameterMap pmap) {
if (isStorageDirMap(pmap)) {
return true;
}
for (Parameter p : pmap) {
switch (p.getName()) {
case ParameterState.SN_ROOT_DIR_PATH:
case ParameterState.SN_ROOT_DIR_SIZE:
return true;
}
}
return false;
}
private static String checkCollectorViolations(String mgmtClass,
boolean collectorEnabled) {
if (collectorEnabled &&
!MgmtUtil.MGMT_JMX_IMPL_CLASS.equals(mgmtClass)) {
return "Can't enable collector service because SN JMX is " +
"disabled.";
}
return null;
}
void replaceRepNodeParams(RepNodeParams repNodeParams) {
/**
* Creation of RepNodes, admins and parameter changes are synchronized
* in order to coordinate changes to the config file(s).
*/
synchronized(this) {
String serviceName = repNodeParams.getRepNodeId().getFullName();
LoadParameters lp =
LoadParameters.getParameters(kvConfigPath, logger);
if (lp.removeMap(serviceName) == null) {
throw new IllegalStateException
("replaceRepNodeParams: RepNode service " + serviceName +
" is not managed by this Storage Node: " + snaName);
}
lp.addMap(repNodeParams.getMap());
lp.saveParameters(kvConfigPath, logger);
}
}
void replaceArbNodeParams(ArbNodeParams arbNodeParams) {
/**
* Creation of ArbNodes, admins and parameter changes are synchronized
* in order to coordinate changes to the config file(s).
*/
synchronized(this) {
String serviceName = arbNodeParams.getArbNodeId().getFullName();
LoadParameters lp =
LoadParameters.getParameters(kvConfigPath, logger);
if (lp.removeMap(serviceName) == null) {
throw new IllegalStateException
("replaceArbNodeParams: ArbNode service " + serviceName +
" is not managed by this Storage Node: " + snaName);
}
lp.addMap(arbNodeParams.getMap());
lp.saveParameters(kvConfigPath, logger);
}
}
void replaceAdminParams(AdminId adminId, ParameterMap params) {
/**
* Creation of RepNodes, admins and parameter changes are synchronized
* in order to coordinate changes to the config file(s).
*/
synchronized(this) {
String serviceName = adminId.getFullName();
LoadParameters lp =
LoadParameters.getParameters(kvConfigPath, logger);
if (lp.removeMap(serviceName) == null) {
if (lp.removeMapByType(ParameterState.ADMIN_TYPE) == null) {
throw new IllegalStateException
("replaceAdminParams: Admin service " + serviceName +
" is not managed by this Storage Node: " +
snaName);
}
}
lp.addMap(params);
lp.saveParameters(kvConfigPath, logger);
}
}
void replaceGlobalParams(GlobalParams gp) {
/**
* Creation of RepNodes, admins and global parameter changes are
* synchronized in order to coordinate changes to the config file(s).
*/
synchronized(this) {
LoadParameters lp =
LoadParameters.getParameters(kvConfigPath, logger);
if (lp.removeMapByType(ParameterState.GLOBAL_TYPE) == null) {
logger.warning("Missing GlobalParams on Storage Node: " +
snaName);
}
lp.addMap(gp.getMap());
lp.saveParameters(kvConfigPath, logger);
globalParams = gp;
globalParameterTracker.notifyListeners(
null, globalParams.getMap());
}
}
/**
* Only a restricted set of parameters can be changed via this mechanism.
* Because there are 2 files changed sequentially there's a slight chance
* of inconsistency in the face of a failure. There's nothing to be done
* for that other than making the call again.
*
* The ParameterMap is one of two types -- normal parameters or a map of
* storage directories. They are handled differently. Normal parameters
* are *merged* into the current map. This mechanism does not allow actual
* removal of parameters, which is the desired semantic. The map
* containing storage directories is replaced, allowing removal.
*
* Because the map of storage directories and some of the SNA parameters are
* also part of the bootstrap state, those parameters are changed as well.
*/
void replaceStorageNodeParams(ParameterMap params) {
synchronized(this) {
/**
* Creation of RepNodes, admins and parameter changes are synchronized
* in order to coordinate changes to the config file(s).
*/
checkSNParams(params,
ConfigUtils.getGlobalParams(kvConfigPath).getMap());
/**
* Apply any changes to BootstrapParams first
*/
changeBootstrapParams(params);
boolean isModified = false;
LoadParameters lp =
LoadParameters.getParameters(kvConfigPath, logger);
ParameterMap curMap = lp.getMap(ParameterState.SNA_TYPE);
if (curMap == null) {
throw new IllegalStateException
("Could not get StorageNodeParams from file: " +
kvConfigPath);
}
StorageNodeParams snp = new StorageNodeParams(curMap);
ParameterMap oldMap = curMap.copy();
/**
* Change params if there are new params
*/
if (!isStorageDirMap(params) &&
!isRNLogDirMap(params)) {
ParameterMap diff = curMap.diff(params, false);
logger.log(Level.INFO,
"replaceStorageNodeParams: changing: {0}", diff);
/**
* Merge the new/modified values into the current map. This
* relies on the fact that the changeable bootstrap params are
* a subset of StorageNodeParams.
*/
if (curMap.merge(params, true) > 0) {
isModified = true;
if (lp.removeMap(curMap.getName()) == null) {
throw new IllegalStateException
("Failed to remove StorageNodeParams from file");
}
lp.addMap(curMap);
}
} else if (isStorageDirMap(params)) {
/**
* If the storage directory map is new or modifies the existing
* one, apply the change. The way to entirely clear a storage
* directory map to pass an empty map rather than a null entry.
* Null means that there is no directory change.
*/
ParameterMap currentMap =
lp.getMap(ParameterState.BOOTSTRAP_MOUNT_POINTS);
if (currentMap == null || !currentMap.equals(params)) {
isModified = true;
if (currentMap != null) {
lp.removeMap(currentMap.getName());
}
logger.log(Level.INFO,
"replaceStorageNodeParams: changing storage " +
"directory map:\n" +
" from: {0}\n" +
" to: {1}",
new Object[]{currentMap, params});
lp.addMap(params);
}
} else if (isRNLogDirMap(params)) {
/**
* If the rn log directory map is new or modifies the existing
* one, apply the change. The way to entirely clear a rn log
* directory map to pass an empty map rather than a null entry.
* Null means that there is no directory change.
*/
ParameterMap currentMap =
lp.getMap(ParameterState.BOOTSTRAP_RNLOG_MOUNT_POINTS);
if (currentMap == null || !currentMap.equals(params)) {
isModified = true;
if (currentMap != null) {
lp.removeMap(currentMap.getName());
}
logger.log(Level.INFO,
"replaceStorageNodeParams: changing rn log " +
"directory map:\n" +
" from: {0}\n" +
" to: {1}",
new Object[]{currentMap, params});
lp.addMap(params);
}
}
/**
* Apply changes to the config file
*/
if (isModified) {
lp.saveParameters(kvConfigPath, logger);
initSNParams(snp);
}
/* Update login policy according to new SNA params */
if (isModified &&
(!isStorageDirMap(params) && !isRNLogDirMap(params))) {
snParameterTracker.notifyListeners(oldMap, params);
}
}
}
/**
* Return the SNA's notion of its current StorageNodeParams and
* GlobalParams, for verification.
*/
LoadParameters getParams() {
LoadParameters lp = LoadParameters.getParameters(kvConfigPath, logger);
return lp;
}
/**
* Returns current information from the SN.
*/
StorageNodeInfo getInfo() {
final StorageNodeInfo sni = new StorageNodeInfo();
final List paths = new ArrayList();
paths.addAll(bp.getStorageDirPaths());
paths.addAll(bp.getRNLogDirPaths());
paths.addAll(bp.getAdminDirPath());
/*
* If there are no explicit storage, rnlog and admin directories,
* this node is using the root directory which we don't report on.
*/
for (String path : paths) {
long size;
try {
size = FileUtils.getDirectorySize(path);
} catch (IllegalArgumentException iae) {
logger.log(Level.SEVERE,
"Exception verifying directory size for " +
path, iae);
size = -1;
}
sni.addStorageDirectory(path, size);
}
return sni;
}
/**
* Initialize locally cached values from the StorageNodeParams. This is
* called at registration time, during startupRegistered, and from
* newParams, so that the new parameters can take effect.
*/
private void initSNParams(StorageNodeParams snp) {
serviceWaitMillis = snp.getServiceWaitMillis();
repnodeWaitSecs = snp.getRepNodeStartSecs();
maxLink = snp.getMaxLinkCount();
linkExecWaitSecs = snp.getLinkExecWaitSecs();
capacity = snp.getCapacity();
logFileLimit = snp.getLogFileLimit();
logFileCount = snp.getLogFileCount();
numCPUs = snp.getNumCPUs();
memoryMB = snp.getMemoryMB();
storageDirectoriesString =
joinStringList(snp.getStorageDirPaths(), ",");
rnLogDirectoriesString =
joinStringList(snp.getRNLogDirPaths(), ",");
adminDirectoryString =
joinStringList(snp.getRNLogDirPaths(), ",");
customProcessStartupPrefix = snp.getProcessStartupPrefix();
/*
* Start the management agent here. This covers the cases of
* startupRegistered and newParams. If nothing regarding the
* configuration of the MgmtAgent has changed, then the factory will
* return the currently established MgmtAgent.
*/
final MgmtAgent newMgmtAgent =
MgmtAgentFactory.getAgent(this, snp, statusTracker);
if (newMgmtAgent != mgmtAgent) {
mgmtAgent = newMgmtAgent;
/*
* If any RepNodes are running at this time, let the new MgmtAgent
* know about them.
*/
for (ServiceManager mgr : repNodeServices.values()) {
/* Update ServiceManager to reference the new MgmtAgent */
mgr.reloadSNParams();
ManagedRepNode mrn = (ManagedRepNode) mgr.getService();
try {
mgmtAgent.addRepNode(mrn.getRepNodeParams(), mgr);
} catch (Exception e) {
String msg = mrn.getResourceId().getFullName() +
": Exception adding RepNode to MgmtAgent: " +
e.getMessage();
throw new IllegalStateException(msg, e);
}
}
/* If there is an admin, announce its existence.*/
if (adminService != null) {
/* Update ServiceManager to reference the new MgmtAgent */
adminService.reloadSNParams();
ManagedAdmin ma = (ManagedAdmin) adminService.getService();
try {
mgmtAgent.addAdmin(ma.getAdminParams(), adminService);
} catch (Exception e) {
String msg = "Exception adding Admin to MgmtAgent: " +
e.getMessage();
throw new IllegalStateException(msg, e);
}
}
/*
* If any ArbNodes are running at this time, let the new MgmtAgent
* know about them.
*/
for (ServiceManager mgr : arbNodeServices.values()) {
/* Update ServiceManager to reference the new MgmtAgent */
mgr.reloadSNParams();
ManagedArbNode man = (ManagedArbNode) mgr.getService();
try {
mgmtAgent.addArbNode(man.getArbNodeParams(), mgr);
} catch (Exception e) {
String msg = man.getResourceId().getFullName() +
": Exception adding ArbNode to MgmtAgent: " +
e.getMessage();
throw new IllegalStateException(msg, e);
}
}
}
}
/* Merge List of String into one String, with delimiter. */
private static String joinStringList(List a, String delimiter) {
String r = "";
if (a != null) {
int n = 0;
for (String s : a) {
if (n++ > 0) {
r += delimiter;
}
r += s;
}
}
return r;
}
/**
* Change bootstrap config file. Filter out non-bootstrap parameters and
* possibly replace the storage directory map. The map is *either* a
* normal parameter map *or* a map of storage directories.
*/
private void changeBootstrapParams(ParameterMap params) {
boolean isModified = false;
File configPath = new File(bootstrapDir, bootstrapFile);
bp = ConfigUtils.getBootstrapParams(configPath, logger);
if (!isStorageDirMap(params)) {
ParameterMap curMap = bp.getMap();
ParameterMap bmap =
params.filter(EnumSet.of(ParameterState.Info.BOOT));
if (!bmap.isEmpty()) {
if (curMap.merge(bmap, true) > 0) {
isModified = true;
}
}
} else {
ParameterMap currentMap = bp.getStorageDirMap();
if (currentMap == null || !currentMap.equals(params)) {
isModified = true;
bp.setStorageDirMap(params);
}
}
if (isModified) {
ConfigUtils.createBootstrapConfig(bp, configPath, logger);
}
}
private static boolean isStorageDirMap(ParameterMap pmap) {
String name = pmap.getName();
return (name != null &&
name.equals(ParameterState.BOOTSTRAP_MOUNT_POINTS));
}
private static boolean isRNLogDirMap(ParameterMap pmap) {
String name = pmap.getName();
return (name != null &&
name.equals(ParameterState.BOOTSTRAP_RNLOG_MOUNT_POINTS));
}
/*
* TODO : Check if isRNLogDirMap and isAdminDirMap functionality
* is needed ?
*/
/**
* Functions for snapshot implementation
*/
/**
* List the snapshots on the SN. Assume that if there is more than one
* managed service they all have the same list, so pick the first one
* found.
*/
String [] listSnapshots() {
ResourceId rid = null;
File repNodeDir = null;
AdminParams ap = ConfigUtils.getAdminParams(kvConfigPath, logger);
if (ap != null) {
rid = ap.getAdminId();
} else {
List repNodes =
ConfigUtils.getRepNodes(kvConfigPath, logger);
for (ParameterMap map : repNodes) {
RepNodeParams rn = new RepNodeParams(map);
rid = rn.getRepNodeId();
repNodeDir = rn.getStorageDirectoryFile();
break;
}
}
if (rid == null) {
logger.warning("listSnapshots: Unable to find managed services");
return new String[0];
}
File snapDir = FileNames.getSnapshotDir(kvRoot.toString(),
getStoreName(),
repNodeDir,
snid,
rid);
if (snapDir.isDirectory()) {
File [] snapFiles = snapDir.listFiles();
String [] snaps = new String[snapFiles.length];
for (int i = 0; i < snaps.length; i++) {
snaps[i] = snapFiles[i].getName();
}
return snaps;
}
return new String[0];
}
String snapshotAdmin(AdminId aid, String name)
throws RemoteException {
if (adminService == null) {
String msg = "AdminService " + aid + " is not running";
logger.warning(msg);
throw new IllegalStateException(msg);
}
try {
ManagedAdmin ma = (ManagedAdmin) adminService.getService();
/*
* Supporting adminDir in snapshot create
*/
final ParameterMap adminMountMap = bp.getAdminDirMap();
final List adminPath =
BootstrapParams.getStorageDirPaths(adminMountMap);
File adminDir = null;
if (!adminPath.isEmpty()){
adminDir = new File(adminPath.get(0));
}
CommandServiceAPI cs = ma.getAdmin(this);
String[] files = cs.startBackup();
String path = null;
try {
path = snapshot(aid, adminDir, name, files);
} finally {
cs.stopBackup();
}
return path;
} catch (RemoteException re) {
logwarning(("Exception attempting to snapshot Admin " + aid), re);
throw re;
}
}
String snapshotRepNode(RepNodeId rnid, String name)
throws RemoteException {
String serviceName = rnid.getFullName();
ServiceManager mgr = repNodeServices.get(serviceName);
if (mgr == null) {
String msg = rnid + ": RepNode is not running";
logger.warning(msg);
throw new IllegalStateException(msg);
}
try {
ManagedRepNode mrn = (ManagedRepNode) mgr.getService();
RepNodeAdminAPI rna = mrn.getRepNodeAdmin(this);
File repNodeDir = mrn.getRepNodeParams().getStorageDirectoryFile();
String[] files = rna.startBackup();
String path = null;
try {
path = snapshot(rnid, repNodeDir, name, files);
} finally {
rna.stopBackup();
}
return path;
} catch (RemoteException re) {
logwarning
(("Exception attempting to snapshot RepNode " + rnid), re);
throw re;
}
}
/**
* Shared code for RN and Admin snapshot removal.
*
* @param rid the ResourceId used to find the snapshot directory
* @param name the name of the snapshot to remove, if null, remove all
* snapshots
*/
void removeSnapshot(ResourceId rid, String name) {
File serviceDir = null;
if (rid instanceof RepNodeId) {
RepNodeParams rnp =
ConfigUtils.getRepNodeParams
(kvConfigPath, (RepNodeId) rid, logger);
if (rnp == null) {
String msg = rid.getFullName() + ": RepNode has not been created";
logger.info(msg);
/* This rep node should have been created by this point. */
throw new IllegalStateException(msg);
}
serviceDir = rnp.getStorageDirectoryFile();
}
/*
* Supporting adminDir in snapshot remove
*/
if (rid instanceof AdminId) {
final ParameterMap adminMountMap = bp.getAdminDirMap();
final List adminPath =
BootstrapParams.getStorageDirPaths(adminMountMap);
if (!adminPath.isEmpty()){
serviceDir = new File(adminPath.get(0));
}
}
File snapshotDir =
FileNames.getSnapshotDir(kvRoot.toString(),
getStoreName(),
serviceDir,
snid,
rid);
if (name != null) {
removeFiles(new File(snapshotDir, name));
} else {
if (snapshotDir.isDirectory()) {
for (File file : snapshotDir.listFiles()) {
logger.info(rid + ": Removing snapshot " + file.getName());
removeFiles(file);
}
}
}
}
/**
* Make hard links from the array of file names in files in the source
* directory to the destination directory. On *nix and Solaris systems the
* ln command can take the form:
* ln file1 file2 file3 ... fileN destinationDir
* This allows a single exec to create links for a number of files,
* amortizing the cost of the exec. Issues to be aware of:
* -- max number of args in a single command line
* -- max length of command line
*/
private void makeLinks(File srcBase, File destDir, String[] files) {
if (isWindows) {
for (String file : files) {
File src = new File(srcBase, file);
File dest = new File(destDir, file);
windowsMakeLink(src, dest);
}
return;
}
int nfiles = 0;
List command = new ArrayList<>();
command.add(LINK_COMMAND);
command.add("-f"); /* overwrites any existing target */
for (String file : files) {
command.add(new File(srcBase, file).toString());
if (++nfiles >= maxLink) {
/*
* Perform the operation and reset.
*/
command.add(destDir.toString());
execute(command);
command = new ArrayList<>();
command.add(LINK_COMMAND);
command.add("-f"); /* overwrites any existing target */
nfiles = 0;
}
}
/*
* Execute the command for the remaining files
*/
if (command.size() > 2) {
command.add(destDir.toString());
execute(command);
}
}
/**
* Make a hard link from src (existing) to dest (the new link) This
* executes "ln" in a new process to perform the link. Ideally it'd be
* built into Java but that's not the case (yet).
*/
private void windowsMakeLink(File src, File dest) {
if (!isWindows) {
throw new IllegalStateException
("Function should only be called on Windows");
}
List command = new ArrayList<>();
command.add("fsutil");
command.add("hardlink");
command.add("create");
command.add(dest.toString());
command.add(src.toString());
execute(command);
}
private void execute(List command) {
/**
* Leave off Logger argument. It makes the log output too verbose
*/
ProcessMonitor pm =
new ProcessMonitor(command, 0, "snapshot", null);
try {
pm.startProcess();
if (!pm.waitProcess(linkExecWaitSecs * 1000)) {
throw new IllegalStateException
("Timeout waiting for ln process to complete");
}
} catch (Exception e) {
logger.info("Snapshot failed to make links with command: " +
command + ": " + e);
throw new SNAFaultException(e);
}
}
/**
* The guts of snapshot shared by RepNode and Admin backup. Assumes that
* DbBackup has been called on the service to get the file array.
* 1. make the target directory
* 2. for each log file in the list, make a hard link from it
* to the target directory
*
* @return the full path to the new snapshot directory
*/
private String snapshot(ResourceId rid,
File serviceDir,
String name,
String [] files) {
File srcBase = FileNames.getEnvDir(kvRoot.toString(),
getStoreName(),
serviceDir,
snid,
rid);
File destBase = FileNames.getSnapshotDir(kvRoot.toString(),
getStoreName(),
serviceDir,
snid,
rid);
File destDir = new File(destBase, name);
logger.info("Creating snapshot of " + rid + ": " + name + " (" +
files.length + " files)");
/**
* Create the snapshot directory, make sure it does not exist first.
*/
if (destDir.exists()) {
String msg =
"Snapshot directory exists, cannot overwrite: " + destDir;
logger.warning(msg);
throw new IllegalStateException(msg);
}
FileNames.makeDir(destDir);
makeLinks(srcBase, destDir, files);
logger.info("Completed snapshot of " + rid + ": " + name);
return destDir.toString();
}
/**
* Recursive delete. Danger!
*/
private void removeFiles(File target) {
if (target.isDirectory()) {
for (File f : target.listFiles()) {
removeFiles(f);
}
}
if (target.exists() && !target.delete()) {
String msg = "Unable to remove file or directory " + target;
logger.warning(msg);
throw new IllegalStateException(msg);
}
}
/**
* This method must not be called for a running service. See if there is
* an environment ready to be used for recovery of the service. If so,
* replace the existing environment, if any, with the new one.
*/
private void checkForRecovery(ResourceId rid, File dir) {
File recoveryDir = FileNames.getRecoveryDir(kvRoot.toString(),
getStoreName(),
dir, snid, rid);
checkForRestoreFromSnapshot(recoveryDir);
if (recoveryDir.isDirectory()) {
File[] files = recoveryDir.listFiles();
if (files.length != 1) {
logger.info(rid + ": only one file is allowed in recovery " +
" directory " + recoveryDir + ", not recovering");
return;
}
if (!files[0].isDirectory()) {
logger.info("Recovery file " + files[0] + " is not a directory"
+ ", cannot use it for recovery");
return;
}
File envDir = FileNames.getEnvDir(kvRoot.toString(),
getStoreName(),
dir, snid, rid);
logger.info(rid + ": recovering from " + files[0] +
" to environment directory " + envDir);
/**
* First rename the old env dir if present. TODO: maybe remove it.
*/
if (envDir.isDirectory()) {
File target = new File(envDir.toString() + ".old");
if (target.exists()) {
removeFiles(target);
}
if (!envDir.renameTo(target)) {
logger.warning(rid + ": failed to rename old env dir");
return;
}
}
if (!files[0].renameTo(envDir)) {
logger.warning(rid + ": failed to rename recovery directory");
}
}
}
/*
* Make recovery directory from the restore snapshot.
* This method is run when user explicitly specify to restore from
* snapshot. If recovery directory already exist, override
* the content with snapshot. Make new file links in the recovery
* directory.
*/
private void checkForRestoreFromSnapshot(File recoveryDir) {
if (restoreSnapshotName == null) {
return;
}
File snapshotDir = FileNames.getSnapshotNamedDir(
recoveryDir.getParentFile(), restoreSnapshotName);
if (!snapshotDir.exists()) {
throw new IllegalStateException(
"Fail to find snapshot directory: " + snapshotDir.toString());
}
/* Replace the recovery directory */
if (recoveryDir.exists()) {
FileUtils.deleteDirectory(recoveryDir);
}
final File destDir = new File(recoveryDir, restoreSnapshotName);
destDir.mkdirs();
makeLinks(snapshotDir, destDir, snapshotDir.list());
}
/**
* Restore configurations from snapshot config. The method will try to
* restore following configurations:
* KVROOT/config.xml
* KVROOT/security.policy
* KVROOT/security (Only in secure store)
* KVROOT/STORENAME/security.policy
* KVROOT/STORENAME/SN/config.xml
*/
private void checkForConfigRecovery() {
if (restoreSnapshotName == null ||
isUpdateConfig == UpdateConfigType.FALSE) {
return;
}
final File snapshotDir =
FileNames.getSnapshotNamedDir(bootstrapDir, restoreSnapshotName);
final SnapshotTaskHandler handler =
new SnapshotFileUtils.SnapshotLockHandler(snapshotDir);
handler.handleTask((SnapshotConfigTask)() -> {
/* mark start snapshot operation */
SnapshotFileUtils.snapshotOpStart(
SnapshotOp.RESTORE, snapshotDir);
/* restore bootstrap config file */
SnapshotFileUtils.restoreSnapshotConfig(
new File(bootstrapDir, bootstrapFile),
snapshotDir, isUpdateConfig);
/* restore security policy file on kvroot */
SnapshotFileUtils.restoreSnapshotConfig(
FileNames.getSecurityPolicyFile(new File(bootstrapDir)),
snapshotDir, isUpdateConfig);
/* Load bootstrap parameters */
final File configPath = new File(bootstrapDir, bootstrapFile);
BootstrapParams bootParam =
ConfigUtils.getBootstrapParams(configPath, logger);
final File snapshotStoreDir = new
File(snapshotDir, bootParam.getStoreName());
final File snapshotSNDir =
FileNames.getStorageNodeDir(
snapshotStoreDir, new StorageNodeId(bootParam.getId()));
/* restore security config */
if (bootParam.getSecurityDir() != null) {
SnapshotFileUtils.restoreSnapshotConfig(
new File(bootstrapDir, bootParam.getSecurityDir()),
snapshotDir, isUpdateConfig);
}
/* restore security policy file on store directory */
SnapshotFileUtils.restoreSnapshotConfig(
FileNames.getSecurityPolicyFile(
FileNames.getKvDir(bootstrapDir,
bootParam.getStoreName())),
snapshotStoreDir, isUpdateConfig);
/* restore SNA config file */
final StorageNodeId id = new StorageNodeId(bootParam.getId());
SnapshotFileUtils.restoreSnapshotConfig(
FileNames.getSNAConfigFile(
bootstrapDir, bootParam.getStoreName(), id),
snapshotSNDir, isUpdateConfig);
/* mark snapshot operation complete */
SnapshotFileUtils.snapshotOpComplete(
SnapshotOp.RESTORE, snapshotDir);
});
}
public MgmtAgent getMgmtAgent() {
return mgmtAgent;
}
public Integer getCapacity() {
return capacity;
}
public int getLogFileLimit() {
return logFileLimit;
}
public int getLogFileCount() {
return logFileCount;
}
public int getNumCpus() {
return numCPUs;
}
public int getMemoryMB() {
return memoryMB;
}
public String getMountPointsString() {
return storageDirectoriesString;
}
public String getRNLogMountPointsString() {
return rnLogDirectoriesString;
}
public String getAdminMountPointsString() {
return adminDirectoryString;
}
public SNASecurity getSNASecurity() {
return snaSecurity;
}
public LoginManager getLoginManager() {
return snaSecurity.getLoginManager();
}
ProcessFaultHandler getFaultHandler() {
return snai.getFaultHandler();
}
public long getCollectorInterval() {
if (globalParams != null) {
return globalParams.getCollectorInterval();
}
DurationParameter dp = (DurationParameter) ParameterState.lookup(
ParameterState.GP_COLLECTOR_INTERVAL).getDefaultParameter();
return dp.toMillis();
}
public int getJsonVersion() {
return jsonVersion;
}
/**
* Create snapshot of configurations. The following configurations will be
* snapshot:
* KVROOT/config.xml
* KVROOT/security.policy
* KVROOT/security (Only in secure store)
* KVROOT/STORENAME/security.policy
* KVROOT/STORENAME/SN/config.xml
*
* @param snapshotName full name of the snapshot.
*/
void createSnapshotConfig(String snapshotName) {
final File snapshotDir =
FileNames.getSnapshotNamedDir(kvRoot, snapshotName);
if (!snapshotDir.exists()) {
snapshotDir.mkdirs();
}
final File snapshotStoreDir = new File(snapshotDir, getStoreName());
if (!snapshotStoreDir.exists()) {
snapshotStoreDir.mkdirs();
}
final File snapshotSNDir =
FileNames.getStorageNodeDir(snapshotStoreDir, getStorageNodeId());
if (!snapshotSNDir.exists()) {
snapshotSNDir.mkdirs();
}
final SnapshotTaskHandler handler =
new SnapshotFileUtils.SnapshotLockHandler(snapshotDir);
handler.handleTask((SnapshotConfigTask)() -> {
/* mark start snapshot operation */
SnapshotFileUtils.snapshotOpStart(
SnapshotOp.SNAPSHOT, snapshotDir);
/* Snapshot bootstrap config file */
SnapshotFileUtils.snapshotConfig(
new File(kvRoot, bootstrapFile), snapshotDir);
/* Snapshot security policy file on root directory */
SnapshotFileUtils.snapshotConfig(
FileNames.getSecurityPolicyFile(kvRoot),
snapshotDir);
/* Snapshot security configs */
if (getSecurityDir() != null) {
SnapshotFileUtils.snapshotConfig(
getSecurityDir(), snapshotDir);
}
/* Snapshot security policy file on store directory */
SnapshotFileUtils.snapshotConfig(
FileNames.getSecurityPolicyFile(
FileNames.getKvDir(kvRoot.toString(), getStoreName())),
snapshotStoreDir);
/* Snapshot SNA config file */
SnapshotFileUtils.snapshotConfig(
kvConfigPath, snapshotSNDir);
/* mark snapshot operation complete */
SnapshotFileUtils.snapshotOpComplete(
SnapshotOp.SNAPSHOT, snapshotDir);
});
}
/**
* Remove the snapshot of configurations. If snapshotName is null, all the
* snapshots under base snapshot directory will be removed.
* @param snapshotName full name of snapshot.
*/
public void removeSnapshotConfig(String snapshotName) {
/* remove snapshot under root */
SnapshotFileUtils.removeSnapshotConfig(kvRoot, snapshotName);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy