/*
* RHQ Management Platform
* Copyright (C) 2005-2014 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.core.pc.configuration;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.rhq.core.clientapi.agent.PluginContainerException;
import org.rhq.core.clientapi.agent.configuration.ConfigurationAgentService;
import org.rhq.core.clientapi.agent.configuration.ConfigurationUpdateRequest;
import org.rhq.core.clientapi.server.configuration.ConfigurationServerService;
import org.rhq.core.clientapi.server.configuration.ConfigurationUpdateResponse;
import org.rhq.core.domain.configuration.Configuration;
import org.rhq.core.domain.configuration.Property;
import org.rhq.core.domain.configuration.RawConfiguration;
import org.rhq.core.domain.resource.ResourceType;
import org.rhq.core.pc.ContainerService;
import org.rhq.core.pc.PluginContainer;
import org.rhq.core.pc.PluginContainerConfiguration;
import org.rhq.core.pc.agent.AgentService;
import org.rhq.core.pc.agent.AgentServiceStreamRemoter;
import org.rhq.core.pc.inventory.InventoryManager;
import org.rhq.core.pc.util.ComponentService;
import org.rhq.core.pc.util.ComponentUtil;
import org.rhq.core.pc.util.FacetLockType;
import org.rhq.core.pc.util.LoggingThreadFactory;
import org.rhq.core.pluginapi.configuration.ConfigurationFacet;
import org.rhq.core.pluginapi.configuration.ResourceConfigurationFacet;
import org.rhq.core.util.MessageDigestGenerator;
/**
* Manages configuration of all resources across all plugins.
*
* <p>This is an agent service; its interface is made remotely accessible if this is deployed within the agent.</p>
*
* @author Jason Dobies
*/
public class ConfigurationManager extends AgentService implements ContainerService, ConfigurationAgentService {
private static final Log log = LogFactory.getLog(ConfigurationManager.class);
private static final String SENDER_THREAD_POOL_NAME = "ConfigurationManager.threadpool";
private static final int FACET_METHOD_TIMEOUT = 60 * 1000; // 60 seconds
private final PluginContainerConfiguration pluginContainerConfiguration;
private final ComponentService componentService;
private final ConfigManagementFactory configMgmtFactory;
private ScheduledExecutorService threadPool;
public ConfigurationManager(PluginContainerConfiguration configuration, ComponentService componentService,
ConfigManagementFactory factory, AgentServiceStreamRemoter streamRemoter, InventoryManager inventoryManager) {
super(ConfigurationAgentService.class, streamRemoter);
this.componentService = componentService;
configMgmtFactory = factory;
pluginContainerConfiguration = configuration;
LoggingThreadFactory threadFactory = new LoggingThreadFactory(SENDER_THREAD_POOL_NAME, true);
threadPool = new ScheduledThreadPoolExecutor(1, threadFactory);
if (pluginContainerConfiguration.getConfigurationDiscoveryInterval() > 0
&& pluginContainerConfiguration.getConfigurationDiscoveryPeriod() > 0
&& pluginContainerConfiguration.isInsideAgent()) {
ConfigurationCheckExecutor configurationChecker = new ConfigurationCheckExecutor(
getConfigurationServerService(), pluginContainerConfiguration.getConfigurationDiscoveryPeriod(),
pluginContainerConfiguration.getConfigurationDiscoveryLimit());
threadPool.scheduleAtFixedRate(configurationChecker,
pluginContainerConfiguration.getConfigurationDiscoveryInitialDelay(),
pluginContainerConfiguration.getConfigurationDiscoveryInterval(), TimeUnit.SECONDS);
}
}
public void shutdown() {
// pass false, so we don't interrupt a plugin in the middle of a config update
PluginContainer.shutdownExecutorService(threadPool, false);
}
public void updateResourceConfiguration(ConfigurationUpdateRequest request) {
ConfigurationServerService configurationServerService = getConfigurationServerService();
try {
ConfigManagement configMgmt = configMgmtFactory.getStrategy(request.getResourceId());
ResourceType resourceType = componentService.getResourceType(request.getResourceId());
Runnable runnable = new UpdateResourceConfigurationRunner(configurationServerService, resourceType,
configMgmt, request);
getThreadPool().submit(runnable);
} catch (PluginContainerException e) {
log.error("Failed to submit config update task. Cause: " + e);
if (configurationServerService != null) {
ConfigurationUpdateResponse error;
error = new ConfigurationUpdateResponse(request.getConfigurationUpdateId(), request.getConfiguration(),
e);
configurationServerService.completeConfigurationUpdate(error);
}
}
return;
}
public ConfigurationUpdateResponse executeUpdateResourceConfigurationImmediately(ConfigurationUpdateRequest request)
throws PluginContainerException {
ConfigurationUpdateResponse response;
try {
ConfigurationServerService configurationServerService = getConfigurationServerService();
ResourceType resourceType = getResourceType(request.getResourceId());
ConfigManagement configMgmt = new LegacyConfigManagement();
configMgmt.setComponentService(componentService);
Callable<ConfigurationUpdateResponse> runner;
runner = new UpdateResourceConfigurationRunner(configurationServerService, resourceType, configMgmt,
request);
response = getThreadPool().submit(runner).get();
} catch (Exception e) {
throw new PluginContainerException("Error occurred in delete resource thread", e);
}
return response;
}
public Configuration merge(Configuration configuration, int resourceId, boolean fromStructured)
throws PluginContainerException {
// TODO Throw an exception if the resource does not support structured and raw
boolean daemonOnly = true;
boolean onlyIfStarted = true;
ResourceConfigurationFacet facet = componentService.getComponent(resourceId, ResourceConfigurationFacet.class,
FacetLockType.READ, FACET_METHOD_TIMEOUT, daemonOnly, onlyIfStarted);
if (fromStructured) {
mergedStructuredIntoRaws(configuration, facet);
} else {
mergeRawsIntoStructured(configuration, facet);
}
return configuration;
}
private void mergeRawsIntoStructured(Configuration configuration, ResourceConfigurationFacet facet) {
Configuration structuredConfig = facet.loadStructuredConfiguration();
if (structuredConfig != null) {
prepareConfigForMergeIntoStructured(configuration, structuredConfig);
for (RawConfiguration rawConfig : configuration.getRawConfigurations()) {
String contents = rawConfig.getContents();
String sha256 = new MessageDigestGenerator(MessageDigestGenerator.SHA_256).calcDigestString(contents);
rawConfig.setContents(contents, sha256);
structuredConfig.addRawConfiguration(rawConfig);
facet.mergeStructuredConfiguration(rawConfig, configuration);
}
}
}
private void prepareConfigForMergeIntoStructured(Configuration config, Configuration latestStructured) {
config.getAllProperties().clear();
for (Property property : latestStructured.getProperties()) {
config.put(property);
}
}
private void mergedStructuredIntoRaws(Configuration configuration, ResourceConfigurationFacet facet) {
Set<RawConfiguration> rawConfigs = facet.loadRawConfigurations();
if (rawConfigs == null) {
return;
}
prepareConfigForMergeIntoRaws(configuration, rawConfigs);
Queue<RawConfiguration> queue = new LinkedList<RawConfiguration>(rawConfigs);
while (!queue.isEmpty()) {
RawConfiguration originalRaw = queue.poll();
RawConfiguration mergedRaw = facet.mergeRawConfiguration(configuration, originalRaw);
if (mergedRaw != null) {
//TODO bypass validation of structured config for template values
String contents = mergedRaw.getContents();
String sha256 = new MessageDigestGenerator(MessageDigestGenerator.SHA_256).calcDigestString(contents);
mergedRaw.setContents(contents, sha256);
updateRawConfig(configuration, originalRaw, mergedRaw);
}
}
}
private void prepareConfigForMergeIntoRaws(Configuration config, Set<RawConfiguration> latestRaws) {
config.getRawConfigurations().clear();
for (RawConfiguration raw : latestRaws) {
config.addRawConfiguration(raw);
}
}
private void updateRawConfig(Configuration configuration, RawConfiguration originalRaw, RawConfiguration mergedRaw) {
configuration.removeRawConfiguration(originalRaw);
configuration.addRawConfiguration(mergedRaw);
}
public Configuration loadResourceConfiguration(int resourceId) throws PluginContainerException {
ConfigManagement loadConfig = configMgmtFactory.getStrategy(resourceId);
Configuration configuration;
try {
configuration = loadConfig.executeLoad(resourceId);
} catch (Throwable t) {
throw new PluginContainerException(createErrorMsg(resourceId, "An exception was thrown."), t);
}
if (configuration == null) {
throw new PluginContainerException(createErrorMsg(resourceId, "returned a null Configuration."));
}
return configuration;
}
private String createErrorMsg(int resourceId, String msg) throws PluginContainerException {
ResourceType resourceType = componentService.getResourceType(resourceId);
return "Plugin Error: Resource Component for [" + resourceType.getName() + "] Resource with id [" + resourceId
+ "]: " + msg;
}
/**
* Returns a thread pool that this object will use when asychronously executing configuration operations on a
* component.
*
* @return a thread pool this object will use
*/
protected ExecutorService getThreadPool() {
return threadPool;
}
/**
* This setter is here to provide a test hook
*
* @param threadPool A fake object such as a mock
*/
void setThreadPool(ScheduledExecutorService threadPool) {
this.threadPool = threadPool;
}
/**
* Given a resource ID, this obtains that resource's ConfigurationFacet interface. If it does not support the
* configuration facet, an exception is thrown.
*
* @param resourceId identifies the resource whose facet is to be returned
* @param lockType how access to the facet is synchronized
* @return the resource's configuration facet component
*
* @throws PluginContainerException on error
*/
protected ConfigurationFacet getConfigurationFacet(int resourceId, FacetLockType lockType)
throws PluginContainerException {
boolean daemonThread = (lockType != FacetLockType.WRITE);
return ComponentUtil.getComponent(resourceId, ConfigurationFacet.class, lockType, FACET_METHOD_TIMEOUT,
daemonThread, true, daemonThread);
}
/**
* Given a resource ID, this obtains that resource's type.
*
* @param resourceId identifies the resource whose type is to be returned
*
* @return the resource's type, if known
*
* @throws PluginContainerException if cannot determine the resource's type
*/
protected ResourceType getResourceType(int resourceId) throws PluginContainerException {
return ComponentUtil.getResourceType(resourceId);
}
/**
* If this manager can talk to a server-side {@link ConfigurationServerService}, a proxy to that service is
* returned.
*
* @return the server-side proxy; <code>null</code> if this manager doesn't have a server to talk to
*/
protected ConfigurationServerService getConfigurationServerService() {
if (pluginContainerConfiguration.getServerServices() != null) {
return pluginContainerConfiguration.getServerServices().getConfigurationServerService();
}
return null;
}
public Configuration validate(Configuration configuration, int resourceId, boolean isStructured)
throws PluginContainerException {
boolean success = true;
boolean daemonOnly = true;
boolean onlyIfStarted = true;
ResourceConfigurationFacet facet = componentService.getComponent(resourceId, ResourceConfigurationFacet.class,
FacetLockType.READ, FACET_METHOD_TIMEOUT, daemonOnly, onlyIfStarted);
if (isStructured) {
try {
facet.validateStructuredConfiguration(configuration);
} catch (IllegalArgumentException e) {
success = false;
} catch (Throwable t) {
throw new PluginContainerException(t.getMessage(), t);
}
} else {
for (RawConfiguration rawConfiguration : configuration.getRawConfigurations()) {
try {
facet.validateRawConfiguration(rawConfiguration);
} catch (IllegalArgumentException e) {
success = false;
rawConfiguration.errorMessage = e.getMessage();
} catch (Throwable t) {
success = false;
rawConfiguration.errorMessage = t.getMessage();
}
}
}
return success ? null : configuration;
}
}