/*
* RHQ Management Platform
* Copyright (C) 2005-2013 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
package org.rhq.enterprise.server.core.comm;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.prefs.BackingStoreException;
import java.util.prefs.InvalidPreferencesFormatException;
import java.util.prefs.Preferences;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.ejb.ConcurrencyManagement;
import javax.ejb.ConcurrencyManagementType;
import javax.ejb.LocalBean;
import javax.ejb.Singleton;
import javax.ejb.Startup;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.management.MBeanServer;
import javax.management.MBeanServerInvocationHandler;
import mazz.i18n.Logger;
import org.jboss.remoting.InvokerLocator;
import org.jboss.util.StringPropertyReplacer;
import org.rhq.core.clientapi.server.configuration.ConfigurationServerService;
import org.rhq.core.clientapi.server.content.ContentServerService;
import org.rhq.core.clientapi.server.discovery.DiscoveryServerService;
import org.rhq.core.clientapi.server.measurement.MeasurementServerService;
import org.rhq.core.domain.cloud.Server;
import org.rhq.core.domain.resource.Agent;
import org.rhq.core.util.PropertiesFileUpdate;
import org.rhq.core.util.exception.ThrowableUtil;
import org.rhq.enterprise.communications.GlobalConcurrencyLimitCommandListener;
import org.rhq.enterprise.communications.Ping;
import org.rhq.enterprise.communications.ServiceContainer;
import org.rhq.enterprise.communications.ServiceContainerConfiguration;
import org.rhq.enterprise.communications.ServiceContainerConfigurationConstants;
import org.rhq.enterprise.communications.ServiceContainerMetricsMBean;
import org.rhq.enterprise.communications.command.client.ClientCommandSender;
import org.rhq.enterprise.communications.command.client.ClientCommandSenderConfiguration;
import org.rhq.enterprise.communications.command.client.ClientRemotePojoFactory;
import org.rhq.enterprise.communications.command.server.CommandProcessorMetrics.Calltime;
import org.rhq.enterprise.communications.command.server.discovery.AutoDiscoveryListener;
import org.rhq.enterprise.communications.util.ConcurrencyManager;
import org.rhq.enterprise.communications.util.SecurityUtil;
import org.rhq.enterprise.server.agentclient.AgentClient;
import org.rhq.enterprise.server.agentclient.impl.AgentClientImpl;
import org.rhq.enterprise.server.cloud.instance.ServerManagerLocal;
import org.rhq.enterprise.server.remote.RemoteSafeInvocationHandler;
import org.rhq.enterprise.server.util.JMXUtil;
import org.rhq.enterprise.server.util.LookupUtil;
/**
* This is an MBean service that can be used to bootstrap the {@link ServiceContainer}. The main purpose for the
* existence of this class is to bootstrap the comm services for the Server so remote CLI and Agent clients
* can talk to the server.
*
* @author John Mazzitelli
*/
@Singleton
@Startup
@LocalBean
@ConcurrencyManagement(ConcurrencyManagementType.BEAN)
@TransactionAttribute(TransactionAttributeType.SUPPORTS)
public class ServerCommunicationsService implements ServerCommunicationsServiceMBean {
/**
* A log for subclasses to be able to use.
*/
private static final Logger LOG = ServerI18NFactory.getLogger(ServerCommunicationsService.class);
private static final String DEFAULT_OVERRIDES_PROPERTIES_FILE = "server-comm-configuration-overrides.properties";
/**
* The remoting subsystem name that identifies remote API client messages.
*/
private static final String REMOTE_API_SUBSYSTEM = "REMOTEAPI";
private static final String WS_REMOTE_API_SUBSYSTEM = "WSREMOTEAPI";
/**
* The MBeanServer where this bootstrap service is registered and where the server-side comm services will be
* registered.
*/
private MBeanServer m_mbs = null;
/**
* The container that manages the server-side services.
*/
private ServiceContainer m_container = null;
/**
* The configuration of the server once started.
*/
private ServerConfiguration m_configuration = null;
/**
* The location of the configuration file - can be a URL, file path or path within classloader.
*/
private String m_configFile = ServerConfigurationConstants.DEFAULT_SERVER_CONFIGURATION_FILE;
/**
* The location of the configuration overrides properties file - can be a URL, file path or path within classloader.
*/
private String m_overridesFile = DEFAULT_OVERRIDES_PROPERTIES_FILE;
/**
* The preferences node name that identifies the configuration set used to configure the services.
*/
private String m_preferencesNodeName = ServerConfigurationConstants.DEFAULT_PREFERENCE_NODE;
/**
* Properties that will be used to override preferences found in the preferences node and the configuration
* preferences file.
*/
private Properties m_configurationOverrides = new Properties();
/**
* A list of known agents.
*/
private KnownAgents m_knownAgents = new KnownAgents();
/**
* A map of clients of known agents. This effectively caches our clients so we don't create more than one sender
* object to the same agent. The key is a string that is produced by {@link #getEndpointKey(String, int)}. Note
* that this cache can be dirty in an HA environment. When deleting an agent (due to a platform uninventory, for
* example) this cache entry will only be cleaned on the primary HA node. As such, we need to be careful to
* ensure cache entries are correct, by doing a quick token match on the cache entry, when accessed.
*/
private Map<String, AgentClient> m_knownAgentClients = new HashMap<String, AgentClient>();
/**
* Where server properties are persisted.
*/
private File m_serverPropertiesFile = null;
/**
* Have the communication services been started
*/
private boolean m_started = false;
/**
* The invocation handler used to process incoming remote API requests from things such as the CLI.
*/
private RemoteSafeInvocationHandler m_remoteApiHandler;
/**
* Actually starts the communications services. Once this returns, agents can communicate with the server. This
* method exists (as opposed to "start()") because we do not want these communications services initialized until
* after we are assured the EJBs are all deployed and we are ready to begin processing incoming agent messages.
*
* Synchronized to ensure that the start operation completes atomically.
*
* @see ServerCommunicationsServiceMBean#startCommunicationServices()
*/
public synchronized void startCommunicationServices() throws Exception {
if (m_started == false) {
// do not rely on the configuration that has been persisted
// we are forcing the configuration file to be reloaded
// if we ever want to change this so the server does pick up
// persisted preferences, replace the following line with:
// config = prepareConfigurationPreferences();
ServerConfiguration config = reloadConfiguration();
ServiceContainer container = (null == m_container) ? new ServiceContainer() : m_container;
AutoDiscoveryListener listener = new ServerAutoDiscoveryListener(m_knownAgents);
container.addDiscoveryListener(listener);
container.start(config.getServiceContainerPreferences().getPreferences(), config
.getClientCommandSenderConfiguration(), m_mbs);
// now let's add our additional handler to support the remote clients (e.g. CLI)
m_remoteApiHandler = new RemoteSafeInvocationHandler();
m_remoteApiHandler.registerMetricsMBean(container.getMBeanServer());
container.addInvocationHandler(REMOTE_API_SUBSYSTEM, m_remoteApiHandler);
m_container = container;
m_configuration = config;
m_started = true;
}
return;
}
/**
* Synchronized to ensure that the stop operation completes atomically.
*
* @see ServerCommunicationsServiceMBean#stop()
*/
public synchronized void stop() {
if (m_container != null) {
// be a good citizen and remove the handler, but ignore if any errors occur and keep going
try {
m_remoteApiHandler.unregisterMetricsMBean(m_container.getMBeanServer());
m_container.removeInvocationHandler(REMOTE_API_SUBSYSTEM);
m_container.removeInvocationHandler(WS_REMOTE_API_SUBSYSTEM);
} catch (Exception e) {
LOG.warn(ServerI18NResourceKeys.REMOTE_API_REMOVAL_FAILURE, ThrowableUtil.getAllMessages(e));
}
m_container.shutdown();
m_container = null;
m_remoteApiHandler = null;
m_started = false;
}
// stop all our clients - any messages flagged with guaranteed delivery will be spooled
synchronized (m_knownAgentClients) {
for (AgentClient client : m_knownAgentClients.values()) {
client.stopSending();
}
m_knownAgentClients.clear();
}
return;
}
/**
* @see ServerCommunicationsServiceMBean#isStarted()
*/
public boolean isStarted() {
return m_started;
}
@PostConstruct
private void init() {
m_mbs = JMXUtil.getPlatformMBeanServer();
JMXUtil.registerMBean(this, OBJECT_NAME);
}
/**
* Cleans up the internal state of this service.
*/
@PreDestroy
private void destroy() {
JMXUtil.unregisterMBeanQuietly(OBJECT_NAME);
m_mbs = null;
m_container = null;
m_configuration = null;
m_configFile = ServerConfigurationConstants.DEFAULT_SERVER_CONFIGURATION_FILE;
m_overridesFile = DEFAULT_OVERRIDES_PROPERTIES_FILE;
m_preferencesNodeName = ServerConfigurationConstants.DEFAULT_PREFERENCE_NODE;
m_configurationOverrides = new Properties();
m_knownAgents.removeAllAgents();
m_knownAgentClients.clear();
m_started = false;
}
@Override
public String getConfigurationFile() {
return m_configFile;
}
@Override
public void setConfigurationFile(String location) {
if (location == null) {
return;
}
m_configFile = replaceProperties(location);
}
@Override
public String getPreferencesNodeName() {
return m_preferencesNodeName;
}
@Override
public void setPreferencesNodeName(String node) {
m_preferencesNodeName = node;
}
@Override
public String getConfigurationOverridesFile() {
return m_overridesFile;
}
@Override
public void setConfigurationOverridesFile(String location) {
if (location == null) {
m_overridesFile = null;
} else {
// substitute ${} replacement variables found in the location string
m_overridesFile = replaceProperties(location);
}
}
private void loadConfigurationOverridesFromFile() throws Exception {
if (m_overridesFile == null) {
return; // nothing to do
}
InputStream is = getFileInputStream(m_overridesFile);
try {
Properties props = new Properties();
props.load(is);
setConfigurationOverrides(props);
} finally {
is.close(); // if we got here, "is" will never be null
}
return;
}
private Properties getConfigurationOverrides() {
return m_configurationOverrides;
}
private void setConfigurationOverrides(Properties overrides) {
if (overrides == null) {
return;
}
m_configurationOverrides.putAll(overrides);
}
@Override
public ServerConfiguration reloadConfiguration() throws Exception {
getPreferencesNode().clear();
return prepareConfigurationPreferences();
}
@Override
public ServerConfiguration getConfiguration() {
return m_configuration;
}
@Override
public synchronized ServiceContainer safeGetServiceContainer() {
if (null == m_container) {
m_container = new ServiceContainer();
}
return m_container;
}
@Override
public ServiceContainer getServiceContainer() {
return m_container;
}
@Override
public String getStartedServerEndpoint() {
if (m_container == null) {
return null;
}
return m_container.getServerEndpoint();
}
@Override
public AgentClient getKnownAgentClient(Agent agent) {
AgentClient agent_client;
if (agent == null) {
throw new IllegalStateException("Agent must be non-null - is a resource not assigned an agent?");
}
// first see if its already cached, if not we need to create one.
synchronized (m_knownAgentClients) {
String agent_address = agent.getAddress();
int agent_port = agent.getPort();
agent_client = m_knownAgentClients.get(getEndpointKey(agent_address, agent_port));
// BZ1071994: Note that this cache can be dirty in an HA environment. As such, we need to ensure cache
// entries are correct. Do a quick token match on the cache entry, when accessed, and recreate as needed.
if ((agent_client == null) || !agent_client.getAgent().getAgentToken().equals(agent.getAgentToken())) {
String remote_uri;
InvokerLocator locator = m_knownAgents.getAgent(agent_address, agent_port);
if (locator != null) {
remote_uri = locator.getLocatorURI();
} else {
// it isn't known via remoting auto-discovery, let's look at the domain object to figure out how to talk to it
remote_uri = agent.getRemoteEndpoint();
if (remote_uri == null) {
remote_uri = "socket://" + agent_address + ":" + agent_port;
}
}
ClientCommandSenderConfiguration client_config = getSenderConfiguration(agent);
ClientCommandSender sender = getServiceContainer().createClientCommandSender(remote_uri, client_config);
agent_client = new AgentClientImpl(agent, sender);
// add the new cache entry, or replace the dirty cache entry (note that dirty cache entries don't
// need to be destroyed as the new one is "logically" the same, but with updated auth info.)
m_knownAgentClients.put(getEndpointKey(agent_address, agent_port), agent_client);
}
}
return agent_client;
}
@Override
public void destroyKnownAgentClient(Agent agent) {
AgentClient agent_client;
// first see if its already cached, if not we need to create one
synchronized (m_knownAgentClients) {
String agent_address = agent.getAddress();
int agent_port = agent.getPort();
agent_client = m_knownAgentClients.remove(getEndpointKey(agent_address, agent_port));
if (agent_client != null) {
agent_client.stopSending();
}
// purge the spool file, if it exists
File spool_file = null;
try {
ClientCommandSenderConfiguration sender_config = getSenderConfiguration(agent);
if (sender_config.commandSpoolFileName != null) {
spool_file = new File(sender_config.dataDirectory, sender_config.commandSpoolFileName);
if (spool_file.exists()) {
// first truncate it, in case Windows is locking it; then try to delete
new FileOutputStream(spool_file, false).close();
spool_file.delete();
}
}
} catch (Exception e) {
LOG.warn("Failed to truncate/delete spool for deleted agent [" + agent + "]"
+ " please manually remove the file: " + spool_file, e);
}
}
return;
}
@Override
public List<InvokerLocator> getAllKnownAgents() {
return m_knownAgents.getAllAgents();
}
@Override
public void addStartedAgent(Agent agent) {
String endpoint = agent.getRemoteEndpoint();
m_knownAgents.addAgent(endpoint);
AgentClient client = getKnownAgentClient(agent);
if (client != null) {
client.startSending(); // we start it now because it allows it to start sending persisted guaranteed delivery messages
}
return;
}
@Override
public void removeDownedAgent(String endpoint) {
m_knownAgents.removeAgent(endpoint);
// since we have been told the agent is down, clear its agent client from cache
// and stop any messages currently being sent or queued to be sent to that agent
AgentClient client;
InvokerLocator locator;
try {
locator = new InvokerLocator(endpoint);
} catch (MalformedURLException e) {
// this should never happen - our endpoint URLs must always be valid
throw new IllegalArgumentException(e);
}
synchronized (m_knownAgentClients) {
client = m_knownAgentClients.remove(getEndpointKey(locator.getHost(), locator.getPort()));
}
if (client != null) {
client.stopSending();
}
return;
}
@Override
public boolean pingEndpoint(String endpoint, long timeoutMillis) {
ClientCommandSender sender = null;
boolean pinged = false;
try {
ServerConfiguration server_config = getConfiguration();
ClientCommandSenderConfiguration client_config = server_config.getClientCommandSenderConfiguration();
// prepare this sender to simply send a ping - do not need any advanced features
client_config.commandSpoolFileName = null;
client_config.enableQueueThrottling = false;
client_config.enableSendThrottling = false;
client_config.serverPollingIntervalMillis = -1;
sender = getServiceContainer().createClientCommandSender(endpoint, client_config);
sender.startSending();
// create our own factory so we can customize the timeout
ClientRemotePojoFactory factory = sender.getClientRemotePojoFactory();
factory.setTimeout(timeoutMillis);
Ping pinger = factory.getRemotePojo(Ping.class);
pinger.ping("", null);
return true;
} catch (Exception e) {
LOG.debug(ServerI18NResourceKeys.PING_FAILED, endpoint, ThrowableUtil.getAllMessages(e, true));
pinged = false;
} finally {
if (sender != null) {
sender.stopSending(false);
}
}
return pinged;
}
@Override
public Integer getGlobalConcurrencyLimit() {
return getServiceContainer().getConfiguration().getGlobalConcurrencyLimit();
}
@Override
public void setGlobalConcurrencyLimit(Integer maxConcurrency) {
if (maxConcurrency == null) {
maxConcurrency = Integer.valueOf(-1);
}
setConcurrencyLimit(GlobalConcurrencyLimitCommandListener.CONCURRENCY_LIMIT_NAME, maxConcurrency, false);
persistServerProperty(ServiceContainerConfigurationConstants.GLOBAL_CONCURRENCY_LIMIT, String
.valueOf(maxConcurrency));
getServiceContainer().getConfiguration().getPreferences().putInt(
ServiceContainerConfigurationConstants.GLOBAL_CONCURRENCY_LIMIT, maxConcurrency);
}
@Override
public Integer getInventoryReportConcurrencyLimit() {
return getServiceContainer().getConcurrencyManager().getConfiguredNumberOfPermitsAllowed(
DiscoveryServerService.CONCURRENCY_LIMIT_INVENTORY_REPORT);
}
@Override
public void setInventoryReportConcurrencyLimit(Integer maxConcurrency) {
setConcurrencyLimit(DiscoveryServerService.CONCURRENCY_LIMIT_INVENTORY_REPORT, maxConcurrency, true);
}
@Override
public Integer getAvailabilityReportConcurrencyLimit() {
return getServiceContainer().getConcurrencyManager().getConfiguredNumberOfPermitsAllowed(
DiscoveryServerService.CONCURRENCY_LIMIT_AVAILABILITY_REPORT);
}
@Override
public void setAvailabilityReportConcurrencyLimit(Integer maxConcurrency) {
setConcurrencyLimit(DiscoveryServerService.CONCURRENCY_LIMIT_AVAILABILITY_REPORT, maxConcurrency, true);
}
@Override
public Integer getInventorySyncConcurrencyLimit() {
return getServiceContainer().getConcurrencyManager().getConfiguredNumberOfPermitsAllowed(
DiscoveryServerService.CONCURRENCY_LIMIT_INVENTORY_SYNC);
}
@Override
public void setInventorySyncConcurrencyLimit(Integer maxConcurrency) {
setConcurrencyLimit(DiscoveryServerService.CONCURRENCY_LIMIT_INVENTORY_SYNC, maxConcurrency, true);
}
@Override
public Integer getContentReportConcurrencyLimit() {
return getServiceContainer().getConcurrencyManager().getConfiguredNumberOfPermitsAllowed(
ContentServerService.CONCURRENCY_LIMIT_CONTENT_REPORT);
}
@Override
public void setContentReportConcurrencyLimit(Integer maxConcurrency) {
setConcurrencyLimit(ContentServerService.CONCURRENCY_LIMIT_CONTENT_REPORT, maxConcurrency, true);
}
@Override
public Integer getContentDownloadConcurrencyLimit() {
return getServiceContainer().getConcurrencyManager().getConfiguredNumberOfPermitsAllowed(
ContentServerService.CONCURRENCY_LIMIT_CONTENT_DOWNLOAD);
}
@Override
public void setContentDownloadConcurrencyLimit(Integer maxConcurrency) {
setConcurrencyLimit(ContentServerService.CONCURRENCY_LIMIT_CONTENT_DOWNLOAD, maxConcurrency, true);
}
@Override
public Integer getMeasurementReportConcurrencyLimit() {
return getServiceContainer().getConcurrencyManager().getConfiguredNumberOfPermitsAllowed(
MeasurementServerService.CONCURRENCY_LIMIT_MEASUREMENT_REPORT);
}
@Override
public void setMeasurementReportConcurrencyLimit(Integer maxConcurrency) {
setConcurrencyLimit(MeasurementServerService.CONCURRENCY_LIMIT_MEASUREMENT_REPORT, maxConcurrency, true);
}
@Override
public Integer getMeasurementScheduleRequestConcurrencyLimit() {
return getServiceContainer().getConcurrencyManager().getConfiguredNumberOfPermitsAllowed(
MeasurementServerService.CONCURRENCY_LIMIT_MEASUREMENT_SCHEDULE_REQUEST);
}
@Override
public void setMeasurementScheduleRequestConcurrencyLimit(Integer maxConcurrency) {
setConcurrencyLimit(MeasurementServerService.CONCURRENCY_LIMIT_MEASUREMENT_SCHEDULE_REQUEST, maxConcurrency,
true);
}
@Override
public Integer getConfigurationUpdateConcurrencyLimit() {
return getServiceContainer().getConcurrencyManager().getConfiguredNumberOfPermitsAllowed(
ConfigurationServerService.CONCURRENCY_LIMIT_CONFIG_UPDATE);
}
@Override
public void setConfigurationUpdateConcurrencyLimit(Integer maxConcurrency) {
setConcurrencyLimit(ConfigurationServerService.CONCURRENCY_LIMIT_CONFIG_UPDATE, maxConcurrency, true);
}
@Override
public Boolean getMaintenanceModeAtStartup() {
InputStream inputStream = null;
Boolean flag;
try {
File file = getServerPropertiesFile();
Properties props = new Properties();
inputStream = new FileInputStream(file);
props.load(inputStream);
flag = Boolean.parseBoolean(props.getProperty(ServerManagerLocal.MAINTENANCE_MODE_ON_STARTUP_PROPERTY,
"false"));
} catch (Exception e) {
LOG.error("Cannot read MM-on-startup property from file, will use the sysprop instead", e);
flag = Boolean.getBoolean(ServerManagerLocal.MAINTENANCE_MODE_ON_STARTUP_PROPERTY);
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (Exception ignored) {
}
}
}
return flag;
}
@Override
public Boolean isMaintenanceModeAtStartup() {
return getMaintenanceModeAtStartup();
}
@Override
public void setMaintenanceModeAtStartup(Boolean flag) {
if (flag == null) {
flag = Boolean.FALSE;
}
persistServerProperty(ServerManagerLocal.MAINTENANCE_MODE_ON_STARTUP_PROPERTY, flag.toString());
System.setProperty(ServerManagerLocal.MAINTENANCE_MODE_ON_STARTUP_PROPERTY, flag.toString());
return;
}
@Override
public void clear() {
getServiceContainerMetricsMBean().clear();
}
@Override
public long getNumberDroppedCommandsReceived() {
return getServiceContainerMetricsMBean().getNumberDroppedCommandsReceived();
}
@Override
public long getNumberNotProcessedCommandsReceived() {
return getServiceContainerMetricsMBean().getNumberNotProcessedCommandsReceived();
}
@Override
public long getNumberFailedCommandsReceived() {
return getServiceContainerMetricsMBean().getNumberFailedCommandsReceived();
}
@Override
public long getNumberSuccessfulCommandsReceived() {
return getServiceContainerMetricsMBean().getNumberSuccessfulCommandsReceived();
}
@Override
public long getNumberTotalCommandsReceived() {
return getServiceContainerMetricsMBean().getNumberTotalCommandsReceived();
}
@Override
public long getAverageExecutionTimeReceived() {
return getServiceContainerMetricsMBean().getAverageExecutionTimeReceived();
}
@Override
public Map<String, Calltime> getCallTimeDataReceived() {
return getServiceContainerMetricsMBean().getCallTimeDataReceived();
}
/**
* Returns a proxy to the {@link ServiceContainerMetricsMBean} that we wrap. We'll pass through its metrics as part
* of our interface. We do this because a plugin's service resource is a one-to-one with a single MBean and we want
* all metrics under a single service.
*
* @return a proxy to the MBean
*/
private ServiceContainerMetricsMBean getServiceContainerMetricsMBean() {
MBeanServer mbs = getServiceContainer().getMBeanServer();
Object proxy = MBeanServerInvocationHandler.newProxyInstance(mbs,
ServiceContainerMetricsMBean.OBJECTNAME_METRICS, ServiceContainerMetricsMBean.class, false);
return (ServiceContainerMetricsMBean) proxy;
}
/**
* Returns the endpoint key based on the given host and port. This is the format of the key to
* {@link #m_knownAgentClients}.
*
* @param host the agent's host
* @param port the agent's port
*
* @return the endpoint's base information that is combined into a single string
*/
private String getEndpointKey(String host, int port) {
return host + ":" + port;
}
/**
* Returns a configuration that can be used for the the client command sender that will talk to the given agent.
*
* @param agent
*
* @return configuration for a {@link ClientCommandSender} that will talk to the given agent
*/
private ClientCommandSenderConfiguration getSenderConfiguration(Agent agent) {
ServerConfiguration server_config = getConfiguration();
ClientCommandSenderConfiguration client_config = server_config.getClientCommandSenderConfiguration();
// make sure it uses a unique spool file. Senders cannot share spool files; each remote endpoint
// must have its own spool file since the spool file contains command requests for a single, specific, agent.
// (a null spool filename means we will never guarantee message delivery to agents)
if (client_config.commandSpoolFileName != null) {
File spool_file = new File(client_config.commandSpoolFileName);
String file_name = spool_file.getName();
String parent_path = spool_file.getParent();
spool_file = new File(parent_path, agent.getName() + "_" + file_name);
client_config.commandSpoolFileName = spool_file.getPath();
}
return client_config;
}
/**
* This will ensure the server's configuration preferences are populated. If need be, the configuration file is
* loaded and all overrides are overlaid on top of the preferences. The preferences are also upgraded to ensure they
* conform to the latest configuration schema version.
*
* @return the server configuration
*
* @throws Exception
*/
private ServerConfiguration prepareConfigurationPreferences() throws Exception {
// load the configuration and start the service container with that configuration
// only load the configuration file if there are no preferences yet - previously existing preferences are reused
Preferences preferences_node = getPreferencesNode();
ServerConfiguration config = new ServerConfiguration(preferences_node);
if (config.getServerConfigurationVersion() == 0) {
config = loadConfigurationFile();
} else {
LOG.debug(ServerI18NResourceKeys.PREFERENCES_ALREADY_EXIST, config.getPreferences());
}
// now that the configuration preferences are loaded, we need to override them with any bootstrap override properties
loadConfigurationOverridesFromFile();
Properties overrides = getConfigurationOverrides();
if (overrides != null) {
for (Map.Entry<Object, Object> entry : overrides.entrySet()) {
String key = entry.getKey().toString();
String value = entry.getValue().toString();
// allow ${var} notation in the values so we can provide variable replacements in the values
value = replaceProperties(value);
// there are a few settings that normally aren't set but can be set at runtime - set them now
value = determineSecurityAlgorithm(value);
preferences_node.put(key, value);
LOG.debug(ServerI18NResourceKeys.CONFIG_PREFERENCE_OVERRIDE, key,
(key.toLowerCase().contains("password")) ? "***" : value);
}
}
// finally, we need to set connector bind address and bind port if they are not set in the properties. Starting
// in 1.1 these fields will typically not be set initially and will need to be overridden here with the
// information defined in the server table for this server.
try {
Preferences preferences = config.getPreferences();
Server server = LookupUtil.getServerManager().getServer();
String bindAddress = preferences.get(ServiceContainerConfigurationConstants.CONNECTOR_BIND_ADDRESS, "");
int bindPort = preferences.getInt(ServiceContainerConfigurationConstants.CONNECTOR_BIND_PORT, -1);
if ("".equals(bindAddress)) {
bindAddress = server.getAddress();
preferences_node.put(ServiceContainerConfigurationConstants.CONNECTOR_BIND_ADDRESS, bindAddress);
}
if (-1 == bindPort) {
String transport = config.getPreferences().get(
ServiceContainerConfigurationConstants.CONNECTOR_TRANSPORT,
ServiceContainerConfigurationConstants.DEFAULT_CONNECTOR_TRANSPORT);
bindPort = (SecurityUtil.isTransportSecure(transport)) ? server.getSecurePort() : server.getPort();
preferences_node.put(ServiceContainerConfigurationConstants.CONNECTOR_BIND_PORT, String
.valueOf(bindPort));
}
} catch (Exception e) {
LOG.error(ServerI18NResourceKeys.ERROR_SETTING_CONNECTOR_COMM_PREFS, e);
ServiceContainerConfiguration scConfig = new ServiceContainerConfiguration(config.getPreferences());
preferences_node.put(ServiceContainerConfigurationConstants.CONNECTOR_BIND_ADDRESS, scConfig
.getConnectorBindAddress());
preferences_node.put(ServiceContainerConfigurationConstants.CONNECTOR_BIND_PORT, String.valueOf(scConfig
.getConnectorBindPort()));
} finally {
try {
preferences_node.flush();
} catch (Exception e) {
LOG.error(ServerI18NResourceKeys.ERROR_FLUSHING_SERVER_PREFS, e);
}
}
// let's make sure our configuration is upgraded to the latest schema
ServerConfigurationUpgrade.upgradeToLatest(config.getPreferences());
LOG.debug(ServerI18NResourceKeys.CONFIG_PREFERENCES, config);
return config;
}
private String determineSecurityAlgorithm(String value) {
String[] algorithmPropNames = new String[] {
ServerConfigurationConstants.CLIENT_SENDER_SECURITY_KEYSTORE_ALGORITHM,
ServerConfigurationConstants.CLIENT_SENDER_SECURITY_TRUSTSTORE_ALGORITHM,
ServiceContainerConfigurationConstants.CONNECTOR_SECURITY_KEYSTORE_ALGORITHM,
ServiceContainerConfigurationConstants.CONNECTOR_SECURITY_TRUSTSTORE_ALGORITHM };
for (String algorithmPropName : algorithmPropNames) {
// if the value is still the ${x} token, it means that setting was not set - let's set it now
if (value.startsWith("${" + algorithmPropName)) {
return System.getProperty("java.vendor", "").contains("IBM") ? "IbmX509" : "SunX509";
}
}
return value;
}
/**
* Loads the {@link #getConfigurationFile() configuration file}. The file location will first be checked for
* existence on the file system and then as a URL. If it cannot be found, it will be assumed the file location
* specifies the file as found in the current class loader and the file will be searched there. An exception is
* thrown if the file cannot be found anywhere.
*
* @return the configuration that was loaded
*
* @throws IOException if failed to load the configuration file
* @throws InvalidPreferencesFormatException if the configuration file had an invalid format
* @throws BackingStoreException if failed to access the preferences persistence store
* @throws Exception on other failures
*/
private ServerConfiguration loadConfigurationFile() throws Exception {
String file_name = getConfigurationFile();
String preferences_node_name = getPreferencesNodeName();
LOG.debug(ServerI18NResourceKeys.PREFERENCES_NODE_NAME, preferences_node_name);
LOG.debug(ServerI18NResourceKeys.LOADING_CONFIG_FILE, file_name);
InputStream config_file_input_stream = getFileInputStream(file_name);
// We need to clear out any previous configuration in case the current config file doesn't specify a preference
// that already exists in the preferences node. In this case, the configuration file wants to fall back on the
// default value and if we don't clear the preferences, we aren't guaranteed the value stored in the backing
// store is the default value.
// But first we need to backup these original preferences in case the config file fails to load -
// we'll restore the original values in that case.
try {
Preferences preferences_node = getPreferencesNode();
ByteArrayOutputStream backup = new ByteArrayOutputStream();
preferences_node.exportSubtree(backup);
preferences_node.clear();
// now load in the preferences
try {
Preferences.importPreferences(config_file_input_stream);
if (new ServerConfiguration(preferences_node).getServerConfigurationVersion() == 0) {
throw new IllegalArgumentException(LOG.getMsgString(
ServerI18NResourceKeys.BAD_NODE_NAME_IN_CONFIG_FILE, file_name, preferences_node_name));
}
} catch (Exception e) {
// a problem occurred importing the config file; let's restore our original values
try {
Preferences.importPreferences(new ByteArrayInputStream(backup.toByteArray()));
} catch (Exception e1) {
// its conceivable the same problem occurred here as with the original exception (backing store problem?)
// let's throw the original exception, not this one
}
throw e;
}
ServerConfiguration server_configuration = new ServerConfiguration(preferences_node);
LOG.debug(ServerI18NResourceKeys.LOADED_CONFIG_FILE, file_name);
return server_configuration;
} finally {
// we know this is not null; if it was, we would have thrown the IOException earlier.
config_file_input_stream.close();
}
}
/**
* Loads a file either from file system, from URL or from classloader. If file
* can't be found, exception is thrown.
* @param file_name the file whose input stream is to be returned
* @return input stream of the file - will never be null
* @throws IOException if the file input stream cannot be obtained
*/
private InputStream getFileInputStream(String file_name) throws IOException {
// first see if the file was specified as a path on the local file system
InputStream config_file_input_stream = null;
try {
File config_file = new File(file_name);
if (config_file.exists()) {
config_file_input_stream = new FileInputStream(config_file);
}
} catch (Exception e) {
// isn't really an error - this just isn't a file on the local file system
}
// see if the file was specified as a URL
if (config_file_input_stream == null) {
try {
URL config_file = new URL(file_name);
config_file_input_stream = config_file.openStream();
} catch (Exception e) {
// isn't really an error - this just isn't a URL
}
}
// if neither a file path or URL, assume the config file can be found in the classloader
if (config_file_input_stream == null) {
config_file_input_stream = Thread.currentThread().getContextClassLoader().getResourceAsStream(file_name);
}
if (config_file_input_stream == null) {
throw new IOException(LOG.getMsgString(ServerI18NResourceKeys.CANNOT_FIND_CONFIG_FILE, file_name));
}
return config_file_input_stream;
}
/**
* Returns the preferences for this server. The node returned is where all preferences are to be stored.
*
* @return the server preferences
*/
private Preferences getPreferencesNode() {
Preferences topNode = Preferences.userRoot().node(ServerConfigurationConstants.PREFERENCE_NODE_PARENT);
Preferences preferencesNode = topNode.node(getPreferencesNodeName());
return preferencesNode;
}
private File getServerPropertiesFile() {
if (m_serverPropertiesFile == null) {
File installDir = LookupUtil.getCoreServer().getInstallDir();
File binDir = new File(installDir, "bin");
m_serverPropertiesFile = new File(binDir, "rhq-server.properties");
}
return m_serverPropertiesFile;
}
private void persistServerProperty(String name, String value) {
String filePath = getServerPropertiesFile().getAbsolutePath();
PropertiesFileUpdate updater = new PropertiesFileUpdate(filePath);
try {
updater.update(name, value);
} catch (IOException e) {
String msgString = LOG.getMsgString(ServerI18NResourceKeys.SERVER_PROPERTY_SAVE_FAILED, name, value,
filePath);
throw new RuntimeException(msgString, e);
}
return;
}
private void setConcurrencyLimit(String limitName, Integer maxConcurrency, boolean persist) {
if (maxConcurrency == null) {
maxConcurrency = Integer.valueOf(-1);
}
if (persist) {
persistServerProperty(limitName, String.valueOf(maxConcurrency));
}
ConcurrencyManager concurrencyManager = getServiceContainer().getConcurrencyManager();
Map<String, Integer> limits = concurrencyManager.getAllConfiguredNumberOfPermitsAllowed();
limits.put(limitName, maxConcurrency);
getServiceContainer().setConcurrencyManager(new ConcurrencyManager(limits));
LOG.info(ServerI18NResourceKeys.NEW_CONCURRENCY_LIMIT, limitName, maxConcurrency);
}
private String replaceProperties(String str) {
if (str == null) {
return null;
}
// keep replacing properties until no more ${} tokens are left that are replaceable
String newValue = "";
String oldValue = str;
while (!newValue.equals(oldValue)) {
oldValue = str;
newValue = StringPropertyReplacer.replaceProperties(str);
str = newValue;
}
return newValue;
}
}