org.rhq.plugins.apache.ApacheServerComponent Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of rhq-apache-plugin Show documentation
Show all versions of rhq-apache-plugin Show documentation
a plugin for managing Apache web servers (1.3 and later)
/*
* 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.plugins.apache;
import static org.rhq.core.domain.measurement.AvailabilityType.DOWN;
import static org.rhq.core.domain.measurement.AvailabilityType.UP;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import net.augeas.Augeas;
import net.augeas.AugeasException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.rhq.augeas.AugeasComponent;
import org.rhq.augeas.config.AugeasConfiguration;
import org.rhq.augeas.config.AugeasModuleConfig;
import org.rhq.augeas.node.AugeasNode;
import org.rhq.augeas.tree.AugeasTree;
import org.rhq.augeas.tree.AugeasTreeBuilder;
import org.rhq.augeas.tree.AugeasTreeException;
import org.rhq.augeas.util.Glob;
import org.rhq.core.domain.configuration.Configuration;
import org.rhq.core.domain.configuration.ConfigurationUpdateStatus;
import org.rhq.core.domain.configuration.Property;
import org.rhq.core.domain.configuration.PropertyList;
import org.rhq.core.domain.configuration.PropertyMap;
import org.rhq.core.domain.configuration.PropertySimple;
import org.rhq.core.domain.configuration.definition.ConfigurationDefinition;
import org.rhq.core.domain.event.EventSeverity;
import org.rhq.core.domain.measurement.AvailabilityType;
import org.rhq.core.domain.measurement.MeasurementDataNumeric;
import org.rhq.core.domain.measurement.MeasurementDataTrait;
import org.rhq.core.domain.measurement.MeasurementReport;
import org.rhq.core.domain.measurement.MeasurementScheduleRequest;
import org.rhq.core.domain.resource.CreateResourceStatus;
import org.rhq.core.pluginapi.configuration.ConfigurationFacet;
import org.rhq.core.pluginapi.configuration.ConfigurationUpdateReport;
import org.rhq.core.pluginapi.event.EventContext;
import org.rhq.core.pluginapi.event.EventPoller;
import org.rhq.core.pluginapi.event.log.LogFileEventPoller;
import org.rhq.core.pluginapi.inventory.CreateChildResourceFacet;
import org.rhq.core.pluginapi.inventory.CreateResourceReport;
import org.rhq.core.pluginapi.inventory.InvalidPluginConfigurationException;
import org.rhq.core.pluginapi.inventory.ResourceComponent;
import org.rhq.core.pluginapi.inventory.ResourceContext;
import org.rhq.core.pluginapi.measurement.MeasurementFacet;
import org.rhq.core.pluginapi.operation.OperationFacet;
import org.rhq.core.pluginapi.operation.OperationResult;
import org.rhq.core.system.OperatingSystemType;
import org.rhq.core.system.ProcessInfo;
import org.rhq.core.system.SystemInfo;
import org.rhq.core.util.file.FileUtil;
import org.rhq.plugins.apache.augeas.ApacheAugeasNode;
import org.rhq.plugins.apache.augeas.AugeasConfigurationApache;
import org.rhq.plugins.apache.augeas.AugeasTreeBuilderApache;
import org.rhq.plugins.apache.mapping.ApacheAugeasMapping;
import org.rhq.plugins.apache.parser.ApacheDirective;
import org.rhq.plugins.apache.parser.ApacheDirectiveTree;
import org.rhq.plugins.apache.util.ApacheBinaryInfo;
import org.rhq.plugins.apache.util.ConfigurationTimestamp;
import org.rhq.plugins.apache.util.HttpdAddressUtility;
import org.rhq.plugins.apache.util.PluginUtility;
import org.rhq.plugins.platform.PlatformComponent;
import org.rhq.plugins.www.snmp.SNMPClient;
import org.rhq.plugins.www.snmp.SNMPException;
import org.rhq.plugins.www.snmp.SNMPSession;
import org.rhq.plugins.www.snmp.SNMPValue;
import org.rhq.plugins.www.util.WWWUtils;
import org.rhq.rhqtransform.AugeasRHQComponent;
/**
* The resource component for Apache 2.x servers.
*
* @author Ian Springer
* @author Lukas Krejci
*/
public class ApacheServerComponent implements AugeasRHQComponent, ResourceComponent,
MeasurementFacet, OperationFacet, ConfigurationFacet, CreateChildResourceFacet {
private static final Log LOG = LogFactory.getLog(ApacheServerComponent.class);
public static final String CONFIGURATION_NOT_SUPPORTED_ERROR_MESSAGE = "Configuration and child resource creation/deletion support for Apache is optional. "
+ "If you switched it on by enabling Augeas support in the connection settings of the Apache server resource and still get this message, "
+ "it means that either your Apache version is not supported (only Apache 2.x is supported) or Augeas is not available on your platform."
+ " Please refer to your agent's log for the precise exception that is causing this behavior. It will logged at error level only once "
+ "per plugin container lifetime";
public static final String PLUGIN_CONFIG_PROP_SERVER_ROOT = "serverRoot";
public static final String PLUGIN_CONFIG_PROP_EXECUTABLE_PATH = "executablePath";
public static final String PLUGIN_CONFIG_PROP_CONTROL_SCRIPT_PATH = "controlScriptPath";
public static final String PLUGIN_CONFIG_PROP_URL = "url";
public static final String PLUGIN_CONFIG_PROP_HTTPD_CONF = "configFile";
public static final String AUGEAS_HTTP_MODULE_NAME = "Httpd";
public static final String PLUGIN_CONFIG_PROP_SNMP_AGENT_HOST = "snmpAgentHost";
public static final String PLUGIN_CONFIG_PROP_SNMP_AGENT_PORT = "snmpAgentPort";
public static final String PLUGIN_CONFIG_PROP_SNMP_AGENT_COMMUNITY = "snmpAgentCommunity";
public static final String PLUGIN_CONFIG_PROP_SNMP_REQUEST_TIMEOUT = "snmpRequestTimeout";
public static final String PLUGIN_CONFIG_PROP_SNMP_REQUEST_RETRIES = "snmpRequestRetries";
public static final String PLUGIN_CONFIG_PROP_ERROR_LOG_FILE_PATH = "errorLogFilePath";
public static final String PLUGIN_CONFIG_PROP_ERROR_LOG_EVENTS_ENABLED = "errorLogEventsEnabled";
public static final String PLUGIN_CONFIG_PROP_ERROR_LOG_MINIMUM_SEVERITY = "errorLogMinimumSeverity";
public static final String PLUGIN_CONFIG_PROP_ERROR_LOG_INCLUDES_PATTERN = "errorLogIncludesPattern";
public static final String PLUGIN_CONFIG_PROP_VHOST_FILES_MASK = "vhostFilesMask";
public static final String PLUGIN_CONFIG_PROP_VHOST_CREATION_POLICY = "vhostCreationPolicy";
public static final String PLUGIN_CONFIG_PROP_RESTART_AFTER_CONFIG_UPDATE = "restartAfterConfigurationUpdate";
public static final String PLUGIN_CONFIG_VHOST_IN_SINGLE_FILE_PROP_VALUE = "single-file";
public static final String PLUGIN_CONFIG_VHOST_PER_FILE_PROP_VALUE = "vhost-per-file";
public static final String PLUGIN_CONFIG_CUSTOM_MODULE_NAMES = "customModuleNames";
public static final String PLUGIN_CONFIG_MODULE_MAPPING = "moduleMapping";
public static final String PLUGIN_CONFIG_MODULE_NAME = "moduleName";
public static final String PLUGIN_CONFIG_MODULE_SOURCE_FILE = "moduleSourceFile";
private static final long DEFAULT_SNMP_REQUEST_TIMEOUT = 2000L;
private static final int DEFAULT_SNMP_REQUEST_RETRIES = 1;
public static final String AUXILIARY_INDEX_PROP = "_index";
public static final String SERVER_BUILT_TRAIT = "serverBuilt";
public static final String AUGEAS_ENABLED = "augeasEnabled";
public static final String DEFAULT_EXECUTABLE_PATH = "bin" + File.separator
+ ((File.separatorChar == '/') ? "httpd" : "Apache.exe");
public static final String DEFAULT_ERROR_LOG_PATH = "logs" + File.separator
+ ((File.separatorChar == '/') ? "error_log" : "error.log");
private static final String ERROR_LOG_ENTRY_EVENT_TYPE = "errorLogEntry";
private static final String[] CONTROL_SCRIPT_PATHS = { "bin/apachectl", "sbin/apachectl", "bin/apachectl2",
"sbin/apachectl2" };
private ResourceContext resourceContext;
private EventContext eventContext;
private SNMPClient snmpClient;
private URL url;
private ApacheBinaryInfo binaryInfo;
private long availPingTime = -1;
private boolean augeasErrorLogged;
private Map moduleNames;
/**
* Delegate instance for handling all calls to invoke operations on this component.
*/
private ApacheServerOperationsDelegate operationsDelegate;
private AvailabilityType lastKnownAvailability;
public void start(ResourceContext resourceContext) throws Exception {
LOG.info("Initializing Resource component for Apache Server [" + resourceContext.getResourceKey() + "]...");
this.resourceContext = resourceContext;
this.eventContext = resourceContext.getEventContext();
this.snmpClient = new SNMPClient();
try {
boolean configured = false;
SNMPSession snmpSession = getSNMPSession();
if (!snmpSession.ping()) {
LOG.warn("Failed to connect to SNMP agent at "
+ snmpSession
+ "\n"
+ ". Make sure\n1) the managed Apache server has been instrumented with the JON SNMP module,\n"
+ "2) the Apache server is running, and\n"
+ "3) the SNMP agent host, port, and community are set correctly in this resource's connection properties.\n"
+ "The agent will not be able to record metrics from apache httpd without SNMP");
} else {
configured = true;
}
Configuration pluginConfig = this.resourceContext.getPluginConfiguration();
String url = pluginConfig.getSimpleValue(PLUGIN_CONFIG_PROP_URL, null);
if (url != null) {
try {
this.url = new URL(url);
if (this.url.getPort() == 0) {
LOG.error("The 'url' connection property is invalid - 0 is not a valid port; please change the value to the "
+ "port the \"main\" Apache server is listening on. NOTE: If the 'url' property was set this way "
+ "after autodiscovery, you most likely did not include the port in the ServerName directive for "
+ "the \"main\" Apache server in httpd.conf.");
} else {
configured = true;
}
} catch (MalformedURLException e) {
throw new InvalidPluginConfigurationException("Value of '" + PLUGIN_CONFIG_PROP_URL
+ "' connection property ('" + url + "') is not a valid URL.");
}
}
if (!configured) {
throw new InvalidPluginConfigurationException(
"Neither SNMP nor an URL for checking availability has been configured");
}
File executablePath = getExecutablePath();
try {
this.binaryInfo = ApacheBinaryInfo.getInfo(executablePath.getPath(),
this.resourceContext.getSystemInformation());
} catch (Exception e) {
throw new InvalidPluginConfigurationException("'" + executablePath
+ "' is not a valid Apache executable (" + e + ").");
}
this.operationsDelegate = new ApacheServerOperationsDelegate(this, pluginConfig,
this.resourceContext.getSystemInformation());
//init the module names with the defaults
moduleNames = new HashMap(ApacheServerDiscoveryComponent.getDefaultModuleNames(binaryInfo
.getVersion()));
//and add the user-provided overrides/additions
PropertyList list = resourceContext.getPluginConfiguration().getList(PLUGIN_CONFIG_CUSTOM_MODULE_NAMES);
if (list != null) {
for (Property p : list.getList()) {
PropertyMap map = (PropertyMap) p;
String sourceFile = map.getSimpleValue(PLUGIN_CONFIG_MODULE_SOURCE_FILE, null);
String moduleName = map.getSimpleValue(PLUGIN_CONFIG_MODULE_NAME, null);
if (sourceFile == null || moduleName == null) {
LOG.info("A corrupted module name mapping found (" + sourceFile + " = " + moduleName
+ "). Check your module mappings in the plugin configuration for the server: "
+ resourceContext.getResourceKey());
continue;
}
moduleNames.put(sourceFile, moduleName);
}
}
startEventPollers();
} catch (Exception e) {
if (this.snmpClient != null) {
this.snmpClient.close();
}
throw e;
}
this.lastKnownAvailability = UP;
}
public void stop() {
this.url = null;
stopEventPollers();
if (this.snmpClient != null) {
this.snmpClient.close();
}
this.lastKnownAvailability = null;
}
public AvailabilityType getAvailability() {
lastKnownAvailability = getAvailabilityInternal();
return lastKnownAvailability;
}
private AvailabilityType getAvailabilityInternal() {
// TODO: If URL is not set, rather than falling back to pinging the SNMP agent,
// try to find a pid file under the server root, and then check if the
// process is running.
boolean available;
try {
if (this.url != null) {
if (LOG.isDebugEnabled()) {
LOG.debug("Trying to ping the server for availability: " + this.url);
}
long t1 = System.currentTimeMillis();
int timeout = PluginUtility.getAvailabilityFacetTimeout();
AvailabilityResult availabilityResult = WWWUtils.checkAvailability(this.url, timeout);
if (availabilityResult.getAvailabilityType() == UP) {
available = true;
} else {
available = false;
if (lastKnownAvailability == UP) {
switch (availabilityResult.getErrorType()) {
case CANNOT_CONNECT:
LOG.warn("Could not connect to Apache server " + resourceContext.getResourceDetails()
+ ", availability will be reported as " + DOWN.name());
break;
case CONNECTION_TIMEOUT:
LOG.warn("Connection to Apache server " + resourceContext.getResourceDetails()
+ " timed out, availability will be reported as " + DOWN.name());
break;
default:
}
}
}
availPingTime = System.currentTimeMillis() - t1;
} else {
if (LOG.isDebugEnabled()) {
LOG.debug("Trying to ping the server for availability through SNMP "
+ getSNMPAddressString(resourceContext.getPluginConfiguration()));
}
available = getSNMPSession().ping();
availPingTime = -1;
}
} catch (Exception e) {
if (LOG.isDebugEnabled()) {
LOG.debug("Exception while checking availability.", e);
}
available = false;
}
if (LOG.isDebugEnabled()) {
LOG.debug("Availability determined: " + (available ? UP : DOWN));
}
return (available) ? UP : DOWN;
}
public void getValues(MeasurementReport report, Set schedules) throws Exception {
SNMPSession snmpSession = getSNMPSession();
boolean snmpPresent = snmpSession.ping();
for (MeasurementScheduleRequest schedule : schedules) {
String metricName = schedule.getName();
if (metricName.equals(SERVER_BUILT_TRAIT)) {
MeasurementDataTrait trait = new MeasurementDataTrait(schedule, this.binaryInfo.getBuilt());
report.addData(trait);
} else if (metricName.equals("rhq_avail_ping_time")) {
if (availPingTime == -1)
continue; // Skip if we have no data
MeasurementDataNumeric num = new MeasurementDataNumeric(schedule, (double) availPingTime);
report.addData(num);
} else {
// Assume anything else is an SNMP metric.
if (!snmpPresent)
continue; // Skip this metric if no SNMP present
try {
//noinspection UnnecessaryLocalVariable
String mibName = metricName;
List snmpValues = snmpSession.getColumn(mibName);
if (snmpValues.isEmpty()) {
LOG.error("No values found for MIB name [" + mibName + "].");
continue;
}
SNMPValue snmpValue = snmpValues.get(0);
boolean valueIsTimestamp = isValueTimestamp(mibName);
if (LOG.isDebugEnabled()) {
LOG.debug("Collected SNMP metric [" + mibName + "], value = " + snmpValue);
}
addSnmpMetricValueToReport(report, schedule, snmpValue, valueIsTimestamp);
} catch (SNMPException e) {
LOG.error("An error occurred while attempting to collect an SNMP metric.", e);
}
}
}
}
private boolean isValueTimestamp(String mibName) {
return (mibName.equals("wwwServiceStartTime"));
}
@Nullable
public OperationResult invokeOperation(@NotNull
String name, @NotNull
Configuration params) throws Exception {
LOG.info("Invoking operation [" + name + "] on server [" + this.resourceContext.getResourceKey() + "]...");
return this.operationsDelegate.invokeOperation(name, params);
}
public Configuration loadResourceConfiguration() throws Exception {
// BZ 858813 - treat Augeas disabled as configuration disabled and just return null, otherwise
// we spam the log.
if (!isAugeasEnabled()) {
LOG.debug(CONFIGURATION_NOT_SUPPORTED_ERROR_MESSAGE);
return null;
}
AugeasComponent comp = getAugeas();
try {
ConfigurationDefinition resourceConfigDef = resourceContext.getResourceType()
.getResourceConfigurationDefinition();
AugeasTree tree = comp.getAugeasTree(AUGEAS_HTTP_MODULE_NAME);
ApacheAugeasMapping mapping = new ApacheAugeasMapping(tree);
return mapping.updateConfiguration(tree.getRootNode(), resourceConfigDef);
} catch (Exception e) {
LOG.error("Failed to load Apache configuration.", e);
throw e;
} finally {
comp.close();
}
}
public void updateResourceConfiguration(ConfigurationUpdateReport report) {
if (!isAugeasEnabled()) {
report.setStatus(ConfigurationUpdateStatus.FAILURE);
report.setErrorMessage(ApacheServerComponent.CONFIGURATION_NOT_SUPPORTED_ERROR_MESSAGE);
return;
}
AugeasComponent comp = getAugeas();
Configuration originalConfig = report.getConfiguration().deepCopy(true);
AugeasTree tree = null;
try {
tree = comp.getAugeasTree(AUGEAS_HTTP_MODULE_NAME);
ConfigurationDefinition resourceConfigDef = resourceContext.getResourceType()
.getResourceConfigurationDefinition();
ApacheAugeasMapping mapping = new ApacheAugeasMapping(tree);
mapping.updateAugeas(tree.getRootNode(), report.getConfiguration(), resourceConfigDef);
tree.save();
LOG.info("Apache configuration was updated");
report.setStatus(ConfigurationUpdateStatus.SUCCESS);
finishConfigurationUpdate(report);
} catch (Exception e) {
if (tree != null) {
LOG.error("Augeas failed to save configuration " + tree.summarizeAugeasError());
e = new AugeasException("Failed to save configuration: " + tree.summarizeAugeasError() + " ", e);
} else
LOG.error("Augeas failed to save configuration", e);
report.setStatus(ConfigurationUpdateStatus.FAILURE);
report.setErrorMessageFromThrowable(e);
if (!originalConfig.equals(report.getConfiguration())) {
LOG.error("Configuration has changed");
} else {
LOG.error("Configuratio has not changed");
}
} finally {
comp.close();
}
}
public AugeasComponent getAugeas() throws AugeasTreeException {
return new AugeasComponent() {
@Override
public AugeasConfiguration initConfiguration() {
File tempDir = resourceContext.getDataDirectory();
if (!tempDir.exists())
throw new RuntimeException("Loading of lens failed");
AugeasConfigurationApache config = new AugeasConfigurationApache(tempDir.getAbsolutePath(),
resourceContext.getPluginConfiguration());
return config;
}
@Override
public AugeasTreeBuilder initTreeBuilder() {
AugeasTreeBuilderApache builder = new AugeasTreeBuilderApache();
return builder;
}
};
}
public CreateResourceReport createResource(CreateResourceReport report) {
if (!isAugeasEnabled()) {
report.setStatus(CreateResourceStatus.FAILURE);
report.setErrorMessage(CONFIGURATION_NOT_SUPPORTED_ERROR_MESSAGE);
return report;
}
if (ApacheVirtualHostServiceComponent.RESOURCE_TYPE_NAME.equals(report.getResourceType().getName())) {
Configuration vhostResourceConfig = report.getResourceConfiguration();
ConfigurationDefinition vhostResourceConfigDef = report.getResourceType()
.getResourceConfigurationDefinition();
Configuration vhostPluginConfig = report.getPluginConfiguration();
String vhostDef = report.getUserSpecifiedResourceName();
String serverName = vhostResourceConfig.getSimpleValue(
ApacheVirtualHostServiceComponent.SERVER_NAME_CONFIG_PROP, null);
//determine the resource key
String resourceKey = vhostDef;
if (serverName != null) {
resourceKey = serverName + "|" + resourceKey;
}
String[] vhostDefs = vhostDef.split(" ");
HttpdAddressUtility.Address addr;
try {
ApacheDirectiveTree parserTree = parseRuntimeConfiguration(true);
Pattern virtualHostPattern = Pattern.compile(".+:([\\d]+|\\*)");
Matcher matcher = virtualHostPattern.matcher(vhostDefs[0]);
if (!matcher.matches())
throw new Exception("Wrong format of virtual host resource name. The right format is Address:Port.");
addr = getAddressUtility().getVirtualHostSampleAddress(parserTree, vhostDefs[0], serverName, false);
} catch (Exception e) {
report.setStatus(CreateResourceStatus.FAILURE);
report.setErrorMessage("Wrong format of virtual host resource name.");
report.setException(e);
return report;
}
String resourceName;
if (serverName != null) {
resourceName = addr.host + ":" + addr.port;
} else {
resourceName = resourceKey;
}
report.setResourceKey(resourceKey);
report.setResourceName(resourceName);
AugeasComponent comp = getAugeas();
//determine the resource name
AugeasTree tree;
try {
tree = comp.getAugeasTree(AUGEAS_HTTP_MODULE_NAME);
//fill in the plugin config
String url = "http://" + addr.host + ":" + addr.port + "/";
vhostPluginConfig.put(new PropertySimple(ApacheVirtualHostServiceComponent.URL_CONFIG_PROP, url));
//determine the sequence number of the new vhost
List existingVhosts = tree.matchRelative(tree.getRootNode(), " includes = tree.matchRelative(tree.getRootNode(), "Include");
AugeasNode include = tree.createNode(tree.getRootNode(), "Include", null,
includes.size() + 1);
tree.createNode(include, "param", vhostFile, 0);
tree.save();
}
try {
vhostFileFile.createNewFile();
} catch (IOException e) {
LOG.error("Failed to create a new vhost file: " + vhostFile, e);
}
comp.close();
comp = getAugeas();
tree = comp.getAugeasTree(moduleConfig.getModuletName());
vhost = tree.createNode(AugeasTree.AUGEAS_DATA_PATH + vhostFile + "/ directives = tree.search("/ServerRoot");
if (!directives.isEmpty())
if (!directives.get(0).getValues().isEmpty())
serverRoot = directives.get(0).getValues().get(0);
SystemInfo systemInfo = this.resourceContext.getSystemInformation();
if (systemInfo.getOperatingSystemType() != OperatingSystemType.WINDOWS) // UNIX
{
// Try some combinations in turn
executableFile = new File(serverRoot, "bin/httpd");
if (!executableFile.exists()) {
executableFile = new File(serverRoot, "bin/apache2");
}
if (!executableFile.exists()) {
executableFile = new File(serverRoot, "bin/apache");
}
} else // Windows
{
executableFile = new File(serverRoot, "bin/Apache.exe");
}
}
return executableFile;
}
/**
* @return The url the server is pinged for availability or null if the url is not set.
*/
public @Nullable
String getServerUrl() {
return resourceContext.getPluginConfiguration().getSimpleValue(PLUGIN_CONFIG_PROP_URL, null);
}
/**
* Returns the httpd.conf file
* @return A File object that represents the httpd.conf file or null in case of error
*/
public File getHttpdConfFile() {
Configuration pluginConfig = this.resourceContext.getPluginConfiguration();
PropertySimple prop = pluginConfig.getSimple(PLUGIN_CONFIG_PROP_HTTPD_CONF);
if (prop == null || prop.getStringValue() == null)
return null;
return resolvePathRelativeToServerRoot(pluginConfig, prop.getStringValue());
}
/**
* Return the absolute path of this Apache server's control script (e.g. "C:\Program Files\Apache
* Group\Apache2\bin\Apache.exe").
*
* On Unix we need to try various locations, as some unixes have bin/ conf/ .. all within one root
* and on others those are separated.
*
* @return the absolute path of this Apache server's control script (e.g. "C:\Program Files\Apache
* Group\Apache2\bin\Apache.exe")
*/
@NotNull
public File getControlScriptPath() {
Configuration pluginConfig = this.resourceContext.getPluginConfiguration();
String controlScriptPath = pluginConfig.getSimpleValue(PLUGIN_CONFIG_PROP_CONTROL_SCRIPT_PATH, null);
File controlScriptFile = null;
if (controlScriptPath != null) {
controlScriptFile = resolvePathRelativeToServerRoot(controlScriptPath);
} else {
boolean found = false;
// First try server root as base
String serverRoot = null;
try {
ApacheDirectiveTree tree = parseRuntimeConfiguration(true);
List directives = tree.search("/ServerRoot");
if (!directives.isEmpty())
if (!directives.get(0).getValues().isEmpty())
serverRoot = directives.get(0).getValues().get(0);
} catch (Exception e) {
LOG.error("Could not load configuration parser.", e);
}
if (serverRoot != null) {
for (String path : CONTROL_SCRIPT_PATHS) {
controlScriptFile = new File(serverRoot, path);
if (controlScriptFile.exists()) {
found = true;
break;
}
}
}
//only try harder on the control script path on OSes with UNIX file system layout
if (!found
&& resourceContext.getSystemInformation().getOperatingSystemType() != OperatingSystemType.WINDOWS) {
String executablePath = pluginConfig.getSimpleValue(PLUGIN_CONFIG_PROP_EXECUTABLE_PATH, null);
if (executablePath != null) {
// this is now something like /usr/sbin/httpd .. trim off the last 2 parts
int i = executablePath.lastIndexOf(File.separatorChar);
if (i >= 0) {
executablePath = executablePath.substring(0, i);
i = executablePath.lastIndexOf(File.separatorChar);
}
if (i >= 0) {
executablePath = executablePath.substring(0, i);
for (String path : CONTROL_SCRIPT_PATHS) {
controlScriptFile = new File(executablePath, path);
if (controlScriptFile.exists()) {
found = true;
break;
}
}
}
}
}
if (!found) {
controlScriptFile = getExecutablePath(); // fall back to the httpd binary
}
}
return controlScriptFile;
}
@NotNull
public ConfigurationTimestamp getConfigurationTimestamp() {
AugeasConfigurationApache config = new AugeasConfigurationApache(resourceContext.getTemporaryDirectory()
.getAbsolutePath(), resourceContext.getPluginConfiguration());
return new ConfigurationTimestamp(config.getAllConfigurationFiles());
}
/**
* This method is supposed to be called from {@link #updateResourceConfiguration(ConfigurationUpdateReport)}
* of this resource and any child resources.
*
* Based on the plugin configuration of this resource, the Apache instance is either restarted or left as is.
*
* @param report the report is updated with the error message and status is set to failure if the restart fails.
*/
public void finishConfigurationUpdate(ConfigurationUpdateReport report) {
try {
conditionalRestart();
} catch (Exception e) {
report.setStatus(ConfigurationUpdateStatus.FAILURE);
report.setErrorMessageFromThrowable(e);
}
}
/**
* This method is akin to {@link #finishConfigurationUpdate(ConfigurationUpdateReport)} but should
* be used in the {@link #createResource(CreateResourceReport)} method.
*
* @param report the report is updated with the error message and status is set to failure if the restart fails.
*/
public void finishChildResourceCreate(CreateResourceReport report) {
try {
conditionalRestart();
} catch (Exception e) {
report.setStatus(CreateResourceStatus.FAILURE);
report.setException(e);
}
}
/**
* Conditionally restarts the server based on the settings in the plugin configuration of the server.
*
* @throws Exception if the restart fails.
*/
public void conditionalRestart() throws Exception {
Configuration pluginConfig = resourceContext.getPluginConfiguration();
boolean restart = pluginConfig.getSimple(PLUGIN_CONFIG_PROP_RESTART_AFTER_CONFIG_UPDATE).getBooleanValue();
if (restart) {
operationsDelegate.invokeOperation("graceful_restart", new Configuration());
}
}
/**
* This method checks whether the supplied node that has been deleted from the tree didn't leave
* the file it was contained in empty.
* If the file is empty after deleting the node, the file is automatically deleted.
* @param tree TODO
* @param deletedNode the node that has been deleted from the tree.
*/
public void deleteEmptyFile(AugeasTree tree, AugeasNode deletedNode) {
File file = tree.getFile(deletedNode);
List fileContents = tree.match(file.getAbsolutePath() + AugeasTree.PATH_SEPARATOR + "*");
if (fileContents.size() == 0) {
file.delete();
}
}
public Map getModuleNames() {
return moduleNames;
}
public ProcessInfo getCurrentProcessInfo() {
return resourceContext.getNativeProcess();
}
public ApacheBinaryInfo getCurrentBinaryInfo() {
return binaryInfo;
}
// TODO: Move this method to a helper class.
static void addSnmpMetricValueToReport(MeasurementReport report, MeasurementScheduleRequest schedule,
SNMPValue snmpValue, boolean valueIsTimestamp) throws SNMPException {
switch (schedule.getDataType()) {
case MEASUREMENT: {
MeasurementDataNumeric metric = new MeasurementDataNumeric(schedule, (double) snmpValue.toLong());
report.addData(metric);
break;
}
case TRAIT: {
String stringValue;
if (valueIsTimestamp) {
stringValue = new Date(snmpValue.toLong()).toString();
} else {
stringValue = snmpValue.toString();
if (stringValue.startsWith(SNMPConstants.TCP_PROTO_ID + ".")) {
// looks like a port - strip off the leading "TCP protocol id" (i.e. "1.3.6.1.2.1.6.")...
stringValue = stringValue.substring(stringValue.lastIndexOf('.') + 1);
}
}
MeasurementDataTrait trait = new MeasurementDataTrait(schedule, stringValue);
report.addData(trait);
break;
}
default: {
throw new IllegalStateException("SNMP metric request has unsupported data type: " + schedule.getDataType());
}
}
}
@NotNull
private File resolvePathRelativeToServerRoot(@NotNull
String path) {
return resolvePathRelativeToServerRoot(this.resourceContext.getPluginConfiguration(), path);
}
//TODO this needs to go...
@NotNull
static File resolvePathRelativeToServerRoot(Configuration pluginConfig, @NotNull
String path) {
File file = new File(path);
if (!FileUtil.isAbsolutePath(path)) {
String serverRoot = getRequiredPropertyValue(pluginConfig, PLUGIN_CONFIG_PROP_SERVER_ROOT);
file = new File(serverRoot, path);
}
// BZ 903402 - get the real absolute path - under most conditions, it's the same thing, but if on windows
// the drive letter might not have been specified - this makes sure the drive letter is specified.
return file.getAbsoluteFile();
}
@NotNull
static String getRequiredPropertyValue(@NotNull
Configuration config, @NotNull
String propName) {
String propValue = config.getSimpleValue(propName, null);
if (propValue == null) {
// Something's not right - neither autodiscovery, nor the config edit GUI, should ever allow this.
throw new IllegalStateException("Required property '" + propName + "' is not set.");
}
return propValue;
}
private void startEventPollers() {
Configuration pluginConfig = this.resourceContext.getPluginConfiguration();
Boolean enabled = Boolean.valueOf(pluginConfig
.getSimpleValue(PLUGIN_CONFIG_PROP_ERROR_LOG_EVENTS_ENABLED, null));
if (enabled) {
File errorLogFile = resolvePathRelativeToServerRoot(pluginConfig.getSimpleValue(
PLUGIN_CONFIG_PROP_ERROR_LOG_FILE_PATH, DEFAULT_ERROR_LOG_PATH));
ApacheErrorLogEntryProcessor processor = new ApacheErrorLogEntryProcessor(ERROR_LOG_ENTRY_EVENT_TYPE,
errorLogFile);
String includesPatternString = pluginConfig.getSimpleValue(PLUGIN_CONFIG_PROP_ERROR_LOG_INCLUDES_PATTERN,
null);
if (includesPatternString != null) {
try {
Pattern includesPattern = Pattern.compile(includesPatternString);
processor.setIncludesPattern(includesPattern);
} catch (PatternSyntaxException e) {
throw new InvalidPluginConfigurationException("Includes pattern [" + includesPatternString
+ "] is not a valid regular expression.");
}
}
String minimumSeverityString = pluginConfig.getSimpleValue(PLUGIN_CONFIG_PROP_ERROR_LOG_MINIMUM_SEVERITY,
null);
if (minimumSeverityString != null) {
EventSeverity minimumSeverity = EventSeverity.valueOf(minimumSeverityString.toUpperCase());
processor.setMinimumSeverity(minimumSeverity);
}
EventPoller poller = new LogFileEventPoller(this.eventContext, ERROR_LOG_ENTRY_EVENT_TYPE, errorLogFile,
processor);
this.eventContext.registerEventPoller(poller, 60, errorLogFile.getPath());
}
}
private void stopEventPollers() {
Configuration pluginConfig = this.resourceContext.getPluginConfiguration();
File errorLogFile = resolvePathRelativeToServerRoot(pluginConfig.getSimpleValue(
PLUGIN_CONFIG_PROP_ERROR_LOG_FILE_PATH, DEFAULT_ERROR_LOG_PATH));
this.eventContext.unregisterEventPoller(ERROR_LOG_ENTRY_EVENT_TYPE, errorLogFile.getPath());
}
public HttpdAddressUtility getAddressUtility() {
String version = getVersion();
return HttpdAddressUtility.get(version);
}
private String getNewVhostFileName(HttpdAddressUtility.Address address, String mask) {
String filename = address.host + "_" + address.port;
String fullPath = mask.replace("*", filename);
File file = getFileRelativeToServerRoot(fullPath);
int i = 1;
while (file.exists()) {
filename = address.host + "_" + address.port + "-" + (i++);
fullPath = mask.replace("*", filename);
file = getFileRelativeToServerRoot(fullPath);
}
return file.getAbsolutePath();
}
private File getFileRelativeToServerRoot(String path) {
File f = new File(path);
if (f.isAbsolute()) {
return f;
} else {
return new File(getServerRoot(), path);
}
}
public ApacheDirectiveTree parseFullConfiguration() {
String httpdConfPath = getHttpdConfFile().getAbsolutePath();
return ApacheServerDiscoveryComponent.parseFullConfiguration(httpdConfPath, binaryInfo.getRoot());
}
public ApacheDirectiveTree parseRuntimeConfiguration(boolean suppressUnknownModuleWarnings) {
String httpdConfPath = getHttpdConfFile().getAbsolutePath();
ProcessInfo processInfo = resourceContext.getNativeProcess();
return ApacheServerDiscoveryComponent.parseRuntimeConfiguration(httpdConfPath, processInfo, binaryInfo,
getModuleNames(), suppressUnknownModuleWarnings);
}
public boolean isAugeasEnabled() {
Configuration pluginConfig = this.resourceContext.getPluginConfiguration();
PropertySimple prop = pluginConfig.getSimple(AUGEAS_ENABLED);
if (prop == null || prop.getStringValue() == null) {
return false;
}
String val = prop.getStringValue();
if (val.equals("yes")) {
Augeas ag = null;
try {
ag = new Augeas();
} catch (Exception e) {
logAugeasError(e);
throw new RuntimeException(CONFIGURATION_NOT_SUPPORTED_ERROR_MESSAGE);
} catch (NoClassDefFoundError e) {
logAugeasError(e);
throw new RuntimeException(CONFIGURATION_NOT_SUPPORTED_ERROR_MESSAGE);
} catch (UnsatisfiedLinkError e) {
logAugeasError(e);
throw new RuntimeException(CONFIGURATION_NOT_SUPPORTED_ERROR_MESSAGE);
} finally {
if (ag != null) {
try {
ag.close();
} catch (Exception e) {
}
ag = null;
}
}
String version = getVersion();
if (!version.startsWith("2.")) {
if (!augeasErrorLogged) {
augeasErrorLogged = true;
LOG.error("Augeas is only supported with Apache version 2.x but version '" + version
+ "' was detected.");
}
throw new RuntimeException(CONFIGURATION_NOT_SUPPORTED_ERROR_MESSAGE);
}
return true;
} else {
return false;
}
}
private void logAugeasError(Throwable cause) {
if (!augeasErrorLogged) {
LOG.error("Augeas is enabled in configuration but was not found on the system.", cause);
augeasErrorLogged = true;
}
}
private String getVersion() {
String ret = resourceContext.getVersion();
if (ret == null) {
//strange, but this happens sometimes when
//the resource is synced with the server for the first
//time after data purge on the agent side
//let's determine the version from the binary info
ret = binaryInfo.getVersion();
}
return ret;
}
ResourceContext getResourceContext() {
return this.resourceContext;
}
}